It would be a rare system that did not provide some variety of <stdlib.h>
Well, no.
<stdlib.h> provides atof(), atoi(), atol(), atoll(), strtod(), strof(), strtolf() functions for converting strings to floating point formats; strtol(), strtoll(), strtoul(), strtoull() functions for converting strings to integer formats; rand() and srand() functions implementing (often poor) PRNG; calloc(), malloc(), realloc(), aligned_alloc() functions for dynamically allocating memory; free() for freeing dynamically allocated memory; abort(), exit(), _Exit(), quick_exit() for program termination; atexit(), at_quick_exit() for registering functions to be executed at program termination; getenv(), setenv(), putenv() for environment management; system() for executing commands using an external command processor; bsearch() and qsort() searching and sorting functions; abs(), labs(), llabs(), div(), ldiv(), lldiv() integer arithmetic functions; mblen(), mbtowc(), wctomb(), mbstowcs(), wcstombs() multibyte/wide character functions.
If you had said
a subset of, then I'd happily fully agree, definitely: all reasonable ones provide malloc(), realloc(), free(), strtol(), strtoul(), atoi(), atol(), and GNU-compatible compilers provide abs(), labs(), llabs() via built-ins.
Some, but not all, provide div() and ldiv(). (These return both the quotient and the remainder. They're rarely used, though.)
Implementations that have sizeof (long long) > sizeof (long), additionally tend to provide strtoll(), strtoull(), llabs(); and if they provide ldiv(), lldiv() too.
Implementations that have either hardware floating point or software-emulated floating point additionally provide atof(), strtof(), strtod(), and if sizeof (long double) > sizeof (double), strtold().
Some provide rand() and srand(), but poor versions. Use a
Xorshift* variant instead: they're almost always faster (no division, just XORs and bit shifts) and more random with longer periods than the linear-congruential PRNGs rand() and srand() traditionally implement.
None provide abort(), exit(), _Exit(), quick_exit(), atexit(), at_quick_exit(), getenv(), putenv(), setenv(), system(), because they just aren't applicable in embedded environments without an operating system.
I don't recall seeing any embedded environment using any of the multibyte/wide character functions, typically because of memory constraints, but Arduino's avr-libc and several arm cores do seem to have at least some sort of support. (The Unicode library defines some 143,859 characters, so it's better to leave multibyte/wide character support to those who really need to implement it.)
I haven't checked on bsearch() and qsort(), but Arduino does provide both via avr-libc; and the arm cores I have installed in Arduino all use newlib for the arm equivalent of avr-libc, providing the same.
See? Yes, parts of <stdlib.h> need to be available for example for memory allocation to be possible, but I've never seen a complete implementation in an embedded environment (as the ten functions related to process termination and environment variables just make no sense in a bare metal environment without an operating system).
I know perfectly well that brucehoult knows the above; I am not questioning his knowledge at all, he knows this stuff in-and-out. I may look like I'm nitpicking, but I really just want to be very careful here, so that new programmers understand that whatever they read about C and C++ usually refers to the standard (hosted) environment, but this here, a mix of freestanding C and C++ but not really either (as the standards define them), is very far from that.
and <string.h>. Or replacements for them, such as Arduino does.
Most of <string.h>, yes. AFAIK, Arduino relies on avr-libc to provide these.
GCC also provides most of them (strcat, strchr, strcmp, strcpy, strcspn, strlen, strncat, strncmp, strncpy, strpbrk, strrchr, strspn, strstr) via built-ins, like explained in the GCC built-in link I provided above.
While avr-libc (for AVRs) and newlib (for ARMs) do provide a more or less complete <string.h>, strcoll(), strxfrm(), strerror() are rarely used and may not be available. (The others are so often used they get implemented sooner or later – although some may implement the BSD versions (see strlcpy()) or strncpy() with strlcpy() semantics.)
I believe it is better to start with the minimum set, so that one gets used to the idea of "standard" functionality not being available, and that when available, the implementation may be suspect. (The avr-libc and newlib floating-point implementation being geared for correctness and being pretty darned slow is kinda-sorta important, when developing for non-hardfloat hardware. It does
not indicate things cannot be done with such hardware, and it does not indicate that software FP is always slow; it's just that these are geared for correctness and not for efficiency. Sometimes, one needs either faster software FP emulation, possibly with tradeoffs (infinity/NaN and rounding mode details being perfect candidates) or a custom fixed-point math implementation, to do the job at hand.)
It is much easier and fun to discover the good (and familiar) parts in existing environments, and be suspicious enough of things like pre-packaged HALs to examine things, so that one can make an informed decision on what they'll trust and work with, and what needs to be (re-)implemented from scratch. This is why I believe it is better to start with the assumption that things may not be available, and then discover that hey, in this particular one they are. If you consider my rationale for this in my previous post, you see why I think this approach works better than the others.
Now, if brucehoult or others have found that other approaches (than the "minimal first" I'm pushing here) work better or have had good experiences with those, please do let me – us! – know. I am often wrong, and even though this approach has shown the most promise and least nasty surprises when I've helped others learn, my experience is still limited, and I for one would be very interested to hear how others have helped navigate this.