[1] for example: https://github.com/websnarf/bstrlib/blob/master/bstrlib.txt
And what value what that be?
... if you're going to say "easier to fit a small implementation into the memory of some microcontroller" - not a sufficient argument. There's always some smaller system with not enough memory, and larger systems with more than enough.
People have a range of technical and aesthetic reasons for hating bloat and C attracts a lot of them.
Off the top of my head:
- Smaller memory footprints
- Easier reimplementation
- Lower attack surface
- Less room for breaking changes
- Less baggage for when we inevitably wish we could deprecate things
wut?
Granted, using C means developers often implement these operations themselves which introduces the possibility of creating more attack surfaces. But it's less likely that the standard library presents an attack surface when the standard library is tiny.
I've been working with it for nearly two decades, and every year I think more that C programs should be confined to a well-guarded quarantined area with hazard trefoils and a "beware of the leopard" sign.
[1] https://en.wikipedia.org/wiki/Greenspun%27s_tenth_rule
edit: people reimplementing their "safe" string library isn't something to brag about, but be ashamed of our entire industry for.
Nobody said you need to link the entire library.
Plus - with custom-made libraries, the memory footprint is no smaller.
> Easier reimplementation
Implementation begins with some part of the standard library, and gets completed gradually later on.
> Lower attack surface
Are you really arguing in favor of "everyone roll your own library" as a reduction of attack surface?
> Less room for breaking changes
C's standard library was already broken in various significant ways to begin with (e.g. gets() ...) .
Also, are you really worried about all those breaking changes between C99 and C11?
> Less baggage for when we inevitably wish we could deprecate things
Umm, you do realize things usually get deprecated when they've been replaced by something more relevant, right? In our case - new C library code.
C++ has a large standard library. This ends up being one of C++'s biggest downfalls: the various libraries don't play nicely with each other, have many different ways of doing the same or similar things, each have complex gotchas--and it's impossible to know them all, so you never really know C++. It's full of dark corners, and the result is bugs, and difficult debugging, and teams that try to enforce only using subsets of the language (and inevitably fail a little). This problem is only getting worse over time. The reason C++'s new features haven't just been backported back into the C standard is that people saw this coming and didn't want it happening. C is already a complex language, but it's still simple enough that you can actually reasonably read the entire C standard library (I have; QED).
A large standard library doesn't have to be this way. Python has a large standard library--this is the "batteries included" of Python. The reason Python can get away with this is that they can deprecate their mistakes. And they are: quite a few modules in the standard library will disappear with the Python 2 sunset. But this is not without downsides--everyone who has been working on a major Python project in the past few years has felt the pain at some point. And C is not Python: it's the backbone of a lot of other applications, so breaking changes are far more costly for C than for Python. C is "once supported, always supported", and for very good reason, so we can't afford to introduce mistakes to the standard.
So, if you don't want the problems of all the cruft in C++'s standard library, and you don't want the deprecations of Python's standard library, your only remaining choice, by process of elimination, is to have a very slim standard library, and make VERY conservative choices about what to add to it.
I think that we should add a new string library to C when strings are a solved problem. But contrary to what people are saying, strings aren't a solved problem. We shouldn't add a string library that doesn't solve internationalization, for example. And in fact, with that problem, the fox is in charge of the henhouse, making the problem worse: we've got people in the Unicode team adding fsking emoji to the standard. Unfortunately, I think we're going to have to wait until Unicode collapses under its own bad decisions and is replaced by a better standard, and that standard becomes mature, before committing to that proven solution. Otherwise we end up with ICU-related crap into the "once supported, always supported" C standard library.
Is it that large? i.e. is it larger than, say, Java's ?
> it's impossible to know them all, so you never really know C++
This is semantic quibbling. You know a language more if you know its commonly-used libraries better - whether they're in the standard or not.
> the various libraries don't play nicely with each other
They may not be in perfect synch, but they play ok-ish-ly with each other - as far as I can tell.
However, not playing nice is much much more of a problem with non-standard libraries - in various languages. I'm sure C programmers have that experience with a vengeance... say, everybody using different implementations of basic data structures like queues and lists and hash tables and so on.
> It's full of dark corners
Yes, yes it is. And they're pretty scary... however, that's not really because of the library size. Also, those dark corners exist, unexplored, for C libraries; it is arguably better that they be mapped once and for all, at least some of them which are somewhat-frequently stumbled upon.
...
More generally - the issue you're describing seems to be more with the combination of "iron-clad backwards compatibility" and a larger standard library:
- C has the former, not the latter - Python has the latter, not the former
C++ has the former (upto minor points like the `auto` keyword), as well as the latter, but with warts.
The warts are there, that's true, but they're not so bad. And use of the standard library does pay off very nicely - often removing warts in your own code.
Large, yes. Larger than Java's? I don't know.
It's larger than C's, that's for sure.
> This is semantic quibbling. You know a language more if you know its commonly-used libraries better - whether they're in the standard or not.
If it's in the standard library you better bet it gets used, even if only by your dependencies.
> I'm sure C programmers have that experience with a vengeance... say, everybody using different implementations of basic data structures like queues and lists and hash tables and so on.
Eh, it's not too bad in C. Libraries tend not to force you to use their data structures too much, and if they do, you just don't use that library. Some, you use their data structures because that's what you included the library to get. It's certainly a problem, but dependency management is easier than having dependencies you can't make go away.
> The warts are there, that's true, but they're not so bad. And use of the standard library does pay off very nicely - often removing warts in your own code.
So use C++, if you like it.
My post wasn't an attack on C++. C++ has its upsides. I only described the downsides of C++ because I was explaining what C is trying to avoid.
A lot of embedded development still have very tight requirements. We're still talking about things discussed in tens or if generous, hundreds of kilobytes.
As a more mainstream example you might have heard of, the Arduino, I think the baseline Arduino has around 32KB available.
Also, in the embedded space, the compilers used are notorious for not having a complete standard library implementation. Lots of things are missing. Thus, developers who care about porting their code to different chips, are cautious about limiting what standard library functions they depend on.
Again, as a more mainstream example you might have heard of, look at Lua, which is implemented in 100% ANSI C. Because they care about Lua truly being portable to everywhere, they try to constrain their dependence on the standard library, and they supply some #ifdefs in luaconf.h to implement or workaround some standard library functions they know to be often missing.
And as a more mainstream example of an incomplete C standard library, look at Android. Bionic is the name of their C standard library. It is missing a lot of things. (And you have to use one of those #ifdefs in luaconf.h to compile Lua successfully on Android to workaround Bionic.)
Unfortunately, I wouldn't call all of it critically useful stuff. For example, "strfry" and "memfrob" are useless beyond toy applications as neither are remotely secure; "l64a" and "a64l" are useless (and possibly even dangerous) because they look like they implement base64 but with a different alphabet (and a much worse API); they have a hilariously large number of random number generators, _none_ of which produce high-quality random numbers for simulation or especially cryptography; etc. etc.
Neither MSVC nor macOS will let you truly statically link a binary (because the system interfaces aren't constant), so it's harder to directly compare, but on macOS "libSystem.dylib" depends on basically everything in /usr/lib/system which is about 5MB of code.
So yeah, I don't think I'd call many real libc libraries "small" by any stretch.
http://www.etalabs.net/compare_libcs.html
For the .a size:
musl: 426k
uClibc: 500k
dietlibc: 120k
If you statically link to these, your final binary will only pull what is actually needed and be smaller than this.
Adding better string functions or CPU-specific optims is nothing compared to that.
musl is mostly so small because it only does the C locale and basic unicode towlower. Nothing like the monstruous glibc unicode tables or internationalized error messages.
If I had to do something like that in a real program I'd very much overalloc a few bytes (because why not? If those small allocations really become a problem in your app you probably want to use a custom allocation scheme anyway) and then just snprintf the result into it. I'd go even further and say that code that would micro-optimize this to save a couple of bytes at the cost of maintainability shouldn't pass a code review IMO.
It's why call it "stupid", "insidious", but "somewhat relevant", as the parent comment proposes a better stdlib in C to handle strings.
There are a lot of positions this would be a great indicator of whether or not the candidate could do the job.
If low level programming is involved the candidate better be able to handle this. Just cause you don't have to solve this problem every doesn't mean you're not going to need those skills for systems programming, writing drivers, embedded programming, etc..
I suspect this would have an extremely high weed out rate actually. A very large % of so called software engineers would fail this, certainly anyone who did all their learning in a dynamic language with automatic garbage collection and who had not branched out into lower level programming.
It's a dumb question if the job position is for a Javascript UI dev of course.. it's not a good question for all positions.
I would also say it's a travesty for any school to award anyone a bachelor's degree in computer science to someone who would fail this question. I've interviewed a large # of candidates with master's degrees who would fail this. Thankfully most of those candidates didn't have a bachelors in computer science + a masters in computer science from a highly ranked US institution.
Also someone who groks this problem and solves it is way less likely to write memory leaks into code in a garbage collected language.
http://www.kylheku.com/cgit/txr/tree/lib.c?id=txr-225#n3897
(The mysterious wini, wref and auto_str macros here allow a C character array on the stack to be a Lisp value with a type tag, recognized and ignored by the garbage collector.)
What I was getting at is that in virtual memory the heap is always contiguous (even if it isn't in physical memory, but we only need it to be contiguous in virtual memory). So one can guarantee that the solution will never require copying data if the program exclusively uses the stack, and the resultant string is the only piece of data on the heap. You always add contiguous memory so your string can dynamically grow without ever copying to a new buffer. This probably requires allocating memory through the use of OS-specific syscalls to request new memory pages instead of malloc or realloc.
While FizzBuzz has become notorious for weeding out total incompetence [1], there are some variations of the problem that are unexpectedly nuanced.
1. https://thedailywtf.com/articles/The-Fizz-Buzz-from-Outer-Sp...
The beauty of the C string is that it is minimal and straightforward: if you have a pointer to it, you know where it ends. And it works with pretty much any OS out there.
(My personal variant of dynamic strings are refcounted, length prefixed and nul-terminated with the pointer pointing to the traditional C string.)
struct string { char *data; size_t size; size_t alloc_size; };
struct string_view { char *data; size_t size; };
And we have the same design that C++ uses.My personal variant uses 32-bits for length and 32-bits for refcount, which aligns well on most platforms, including 64-bit ones.
Something like:
struct string_header
{
int32_t refcnt;
int32_t length;
// character content starts here
};
Given a char pointer to get to the header you must do: ((struct string_header *)p)[-1]To clarify this is part of a larger library that includes other memory management machinery, such as autorelease pools, etc.
>I
>I
There is no 'I' in standard libraries :)
struct string {
union {
struct {
char *ptr;
size_t capacity;
};
char str[16];
} data;
size_t length;
};
It looks complex, but it’s actually a really nice design because it requires no separate allocation for strings less than 16 bytes long, which is a common case, and the strings are relatively compact. And, it stores a capacity parameter which allows you to know when it’s safe to grow the string without allocating, making it possible to implement efficient repeated concatenation.size_t, or uintptr_t [1]. Maybe uint32_t, although given pointer alignment, smaller-than-pointer for size doesn't help all that much.
> The beauty of the C string is that it is minimal and straightforward: if you have a pointer to it, you know where it ends.
And if you screw up adding a null byte, client code will happily keep running through the rest of memory to find it. And if your string has embedded nulls, you're SOL anyways.
> And it works with pretty much any OS out there.
And for safety reasons, the OS generally has to assume a maximum length anyways, otherwise, you tend to have security vulnerabilities.
[1] size_t is not necessarily the same size as uintptr_t. An architecture with a 64-bit base pointer shared across several 32-bit indexes is an interesting idea that could use some more programming exploration.
The good thing about 64 bit lengths is you never need to worry about overflowing the string. The bad thing is no one know what to do, or codes for the possibility of the string overflowing. 8 bits 'should' avoid people making that assumption.
Something anything is better than the total shit design we've been living with.
Exactly like what is returned by strlen... ?
There was a quiet market war some forty years ago about that, and Pascal lost.
But yeah, now we can afford it, the downsides vastly exceed the vanishing cost.
Then compilers wanting to support the "with-batteries" superset standard could just allow their flag that looks like "-std=c90" to be extended like "-std=c90+batteries".
What this means is that:
• people who are learning a language, can learn language features "through" examples that rely on the included batteries to demonstrate a point (for example, image support in DrRacket, or the HTTP client in Go), without needing to also learn everything involved in ecosystem package management first;
• people who want to write small, self-contained, yet portable utility programs (e.g. coreutils), can just rely on the language runtime and its presence on basically every OS, rather than declaring package dependencies (= not portable) or statically linking in their own libraries (= not small). The more stuff a language's runtime does, the more such programs become possible to write in said language.
• features in a shared runtime can rely on other things in a shared runtime; and the library ecosystem of a runtime can use the runtime's data-structures as a lingua franca to specify library APIs in terms of. JS libraries return promises because the JS runtime includes promises. Elixir libraries pass around DateTimes because the Elixir runtime specifies a DateTime type. Go libraries take and return slices, maps, and channels because those are things that exist in the runtime. If these runtimes didn't have these things—even if the languages had fancy macro systems that meant that pulling in the relevant library would enable exactly the same syntax—then library support for these would be fragmented, rather than expected. Exactly the way library support for prefix-length strings is in C.
Or you can JUST use D.
Why people always skip enormous work already done decades ago and whine that they miss something and something in C?
sys/queue.h provides four different linked list implementations: standard singly-linked lists, standard doubly-linked lists, a singly-linked list with a tail pointer (for fast tail insertion) and a doubly-linked list without a head pointer. All come complete with
sys/tree.h provides two types of balanced search trees: splay trees and red-black trees. Both provide ordered dictionaries
Both libraries make quite extensive use of macros, but they are quite reasonably implemented and documented via man-page. I've successfully used them a few times. Shame that, like other nice BSD innovations (e.g. strlcpy/strlcat) they haven't made it into other libcs.
They've been moved to the Xcode Command Line Tools with the rest of the standard headers.
Off-topic, I know, but what's the benefit of this and how does it differ from a doubly-linked list without a tail pointer?
I understand there are benefits to small or no changes whatsoever.
I also understand there are a number of standards (1990, 95, 2011...)
Is it that all the people who would be interested in improving C just naturally migrate to C++ which is getting improvements?
Your example of better strings is good, but why not lists and hashes as language elements?
If not in the language directly, then ok in the library. But speaking of the library, why limit to just a few things, why not batteries fully included. shoot for knuth in the standard library!
C has an anemic standard library but many libraries you can use as your standard, this keeps everyone happy. I'd expect the javascript/npm world to evolve this way in future.
C is a bit like a Formula One car. Regardless of the historic reasons that caused it to evolve the way it did, it ended up occupying a niche and being very well suited to it, while at the same time being ill suited for other, more general purpose, uses. Under that perspective, change is slow because the people most invested in it want to make improvement happen in a very narrow and precise direction. McLaren, after all, is not going to bat an eye if you complain about its latest model lacking a baby seat!!!
C++, on the other hand, started sort of like NASCAR racing. It wanted to make a racing car out of a mundane, everyday car, and it got very, very good at it. Unfortunatelly, because C++ is based in an everyday car, and because it is Designed-by-Committee (TM), it shows lots and lots of "improvements" that individually kind of make sense, but in the bulk lack any coherence. Nowadays, C++ may perform like the Batmobile (from "Batman Begins" movie) on a good day; but you never know when it is going to bite you in the ass and turn into the Homermobile from the 90's Simpson's TV show.
It is a mix of necessity and lack of insight that many of the people in my generation had to learn how to drive in fucking racing carts!!! But when it's all said and done it gives you a little perspective on how things work and helps you appreciate the differences.
Probably because there are many ways to implement data structures, for very specific, and entirely different use cases. C gives you the minimum that allows you to build those according to your own specific needs. It would be very difficult or perhaps even impossible to come up with an implementation that suits everyone's specific cases. Perhaps we could do it by giving the users the option to pick, but then the language would be really huge, it would quickly become bloated. Think about how many ways one could implement a hash table! I like and use ohash extensively though, along with stuff from sys/queue.h. For hash tables, uthash seems to be popular, too.
> If not in the language directly, then ok in the library. But speaking of the library, why limit to just a few things, why not batteries fully included. shoot for knuth in the standard library!
There are many libraries out there. GLib comes to mind. There is also Gnulib, Klib, and God knows what else. I am sure there are a lot of libraries. I have written my own private library of common data structures, subroutines, and so on. You may want to take a look at: https://github.com/kozross/awesome-c
What is the type of the length?
What is the type of the length+string?
What lengths is it limited to?
Do we need a 32 bit and 64 bit version? And 128+ bit version?
Can you provide an implementation? What is its best and worst case efficiency?
What is its expected efficiency for the typical string operations?
I think that the null-terminated representation of the raw string data is a fantastic design; the best of all conceivable alternatives.