Yep, ISO C should get an optional library, that embedded systems may want to skip, offering things like: dynamic strings, linked lists with iterator, an ordered dictionary data structure with iterator (API will be like a hash table basically), a PRNG that is not a joke. With just that it will feel like programming in a different language.
Several flavours of BSD provide header-only libraries for linked lists and search trees as part of their libc in sys/queue.h and sys/tree.h, which are remarkably nice and easy to use. newlib libc provides these, and macOS used to provide them in /usr/include (now they're only in the kernel headers - but they're header-only and self-contained so they're easy to add to any project).
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.
> macOS used to provide them in /usr/include (now they're only in the kernel headers - but they're header-only and self-contained so they're easy to add to any project)
They've been moved to the Xcode Command Line Tools with the rest of the standard headers.
ISO C already mandates that most of its library is optional for "freestanding" implementations. (float.h, iso646.h, limits.h, stdalign.h, stdarg.h, stdbool.h, stddef.h, stdint.h, and stdnoreturn.h are the only headers required even for freestanding implementations).
What if we just created a standard [and related working group] that exists "downstream" of ISO C, where there is one release of the "C-with-batteries" per ISO C release (e.g. Cwb11 would descend from C11; Cwb18 from C18; etc.) that just adds these sort of "obvious" libc parts (or libcwb parts, I guess) that the C standards committees don't seem to want to bring into the "base" libc, without changing C itself otherwise?
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".
If every C compiler and C runtime shipped with CoreFoundation, I'd agree with you. Some undertakings are too enormous for 100 different implementors to want to go through the trouble of making their own implementations of it!
The whole point of libc—or any language runtime—is that it's "just there." In other words, insofar as a piece of code "is C", it can expect to have access to these libraries; and insofar as a compiler "is a C compiler", it will be expected to link your code to a runtime that includes these libraries.
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.
string literals without having to process a asciz string into a ptr+size string at runtime, would be nice. C++ has language support that could enable that regardless of a future string format, of course, but C doesn't.