But one of the best features of C is that you can mostly ignore the standard library and still enjoy "C the language", e.g. nobody ever choose C for its standard library ;)
(also re UB etc...: use the mighty trio UBSAN, ASAN and TSAN!)
What is the standard library for the C which ends all the other standards libs?
Same for string handling/processing, there are specialized libraries out there which do a specific job better than the rather generic standard library functions (this is also true for the C++ stdlib), one just has to find those libraries.
PS: I don't think that a "batteries included" standard library (like python has) would even make sense for C, such a library would need to be opinionated by definition, better to keep (too many) opinions out of the standard, as this road just leads to another C++ ;)
I actually can't think of a lot of these that are super common across projects.
ICU is probably the most notable one, with its particular focus on unicode correctness.
A lot of large projects end up writing their own string library of sorts.
There isn't a universally recognized "standard library replacement" (and people argue about what parts of the standard library they don't like), but here:
https://stackoverflow.com/questions/486383/safer-alternative...
are a few options:
* Glib (the basis for Gtk): https://developer.gnome.org/glib/stable/glib.html
* The Apache Portable Runtime (APR): http://apr.apache.org/
Caveat: I haven't used them.
C is one of the hardest languages to do this with given its anaemic dependency management.
> (also re UB etc...: use the mighty trio UBSAN, ASAN and TSAN!)
All of which will miss some cases, even in combination.
Because C is so bare-bones, it usually leans on POSIX as its extended standard library, and that is also full of old cruft.
https://en.cppreference.com/w/c/string/byte/strtok
About getenv: This sort-of fixed with `getenv_s()` in C11
> Microsoft Visual Studio implements an early version of the APIs. However, the implementation is incomplete and conforms neither to C11 nor to the original TR 24731-1. For example, it doesn't provide the set_constraint_handler_s function but instead defines a _invalid_parameter_handler _set_invalid_parameter_handler(_invalid_parameter_handler) function with similar behavior but a slightly different and incompatible signature. It also doesn't define the abort_handler_s and ignore_handler_s functions, the memset_s function (which isn't part of the TR), or the RSIZE_MAX macro.The Microsoft implementation also doesn't treat overlapping source and destination sequences as runtime-constraint violations and instead has undefined behavior in such cases.
[0]: http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1967.htm
When I learned C (early 90's), multi-threading wasn't even something you considered. It was either multiple processes or an event loop with select/poll.
In a localized world searching for delimiters also starts to make less sense. Eg. I have been told it doesn't make sense to break on whitespace for Chinese text.
To be clear, I am not saying these functions are bad or evil or to blame for their limitations (a bunch of the problem space wasn't invented yet when they were introduced), just noting they have limits. A bunch of more recent languages and libraries have the same or similar issues, too.
> I have been told it doesn't make sense to break on whitespace for Chinese text.
It could make sense to break on whitespace in some programming language or data format that allows Chinese (and other) identifiers.
A command interpreter that allows Chinese arguments (such as file names) wants to break on spaces, as usual.
> if a delimiter appears as part of a multi byte sequence, you may see strange results.
UTF-8 was designed by a dyed-in-the-wool C-and-Unix engineer, who ensured that such a thing can't happen. No character in the 0x00-0x7F range can occur in a multi-byte character.
The byte which starts a UTF-8 character cannot occur anywhere other than at the start, which is why we can use strstr to look for it.
UTF-8 continuation characters are limited to the range \200 through \300 so there's basically zero chance that if you choose something like comma as your delimiter that it's going to tokenize the middle of a multibyte sequence.
Also take into consideration that, under the hood, functions like strpbrk() are typically accelerated by CPU instructions such as PCMPISTRI which doesn't support UTF-8 natively but it does support UCS-2.
Not just "basically;" there is no possible collision between ASCII characters and any valid multibyte encoding. This can be seen somewhat visually in this table[1] and is an intentional aspect of the UTF-8 design.
On joiners / combining characters: I'd encourage using composed normalization (NFC) rather than decomposed normalization (NFD).
Just curiosity: are there any glyphs that lack a single codepoint representation, where one of the joined codepoints is an ASCII character? (That only helps after normalization, of course.)
The ISO C library string handling stuff is for systems programming, not for scanners and parsers for natural written language.
I quit using it myself because I could never remember just what the exact protocol was for 0.
[1] https://github.com/coreutils/coreutils/blob/master/src/yes.c
But of course GNU is kind of notorious in this regard. Compare their `yes` to OpenBSD's. It's night and day.
https://github.com/openbsd/src/blob/master/usr.bin/yes/yes.c
For reference, the universe is about 13.787 billion years old [0]. That's about 13.787 * 10^9 * 365 * 24 * 3600 = 4.348 * 10^17 seconds, which (I think?) is a 59-bit number [1]. 10ths of a second will require 62 bits, which is right about at the edge of what a 64-bit signed integer will allow.
If you want milliseconds, you'll need at least 69 bits. For nanoseconds, you'll need at least 89 bits.
So you'll either need an integer type that's wider than what's natively supported in most hardware (thus potentially sacrificing performance), or you'll have to sacrifice precision.
[0]: https://en.wikipedia.org/wiki/Age_of_the_universe
[1]: https://www.wolframalpha.com/input/?i=13.787+*+10%5E9+*+365+...
I'm mostly kidding. But if you're thinking of representing nanoseconds since the big bang, the wait for 128-bit CPUs is not very long...
[0] https://gcc.gnu.org/onlinedocs/gcc/_005f_005fint128.html
Since larger than 64 bit ints are a disaster for portability, the reasonable solution is to go with a 64 bit signed seconds, 32 bit nano offset field. A lot of language std libs have adopted something along these lines.
DJB was advocating for everything to be in a format like this, referenced to TAI (UTC without leap seconds, basically). Sadly that didn't get any traction.
I was horrified even more when i learned that future leap seconds are undefined, and we literally can't tell what is the time on the clock lik3w a million seconds from now.
What do people that need higher resolution use? And the people that don't care about that amount, do they pay the performance penalty?
Every physical or humane value is hard to some extent.
If you want to know what's broken? Most real time clock modules. They almost all want to store time as HH:MM:SS MM:DD:YY and sometimes 1/256 of a second but sometimes not.
I don't think it has really been proposed anywhere though, unfortunately.
What type (and how big) should length be?
size_t.
I can understand that this might have been too much implementation complexity/risk to contemplate 40 years ago, but this kind of pattern is very well established at this point, especially in scripting languages with loose typing.
It takes more code to use it correctly than to hand-code what you think it is supposed to be doing for you. strtok is Cursed.
strlcpy is similar. If you don't write that much more code, you are not using it correctly, and it is not giving you the value that is the reason you thought was why you were using it.
That's not really a counter-example. Sure the string handling was an unfortunately choice but it is the standard. The standard library must implement it as defined. Being standard-compliant is not lack of polish.
rust-lang saves the day, yet again https://www.brandonsmith.ninja/blog/favorite-rust-function
Wrong. Counter-example: locales [1]
Also: String handling, which is responsible for so, so, so many security vulnerabilities. The underlying cause is zero-terminated strings (instead of using start/end pairs or start/length pairs). You can't even tokenize a string without either copying or modifying it!
Also also: just a single, apologetic mention of undefined behavior. Responsible – in cooperation with over-zealous compiler writers – for so many more bugs not already caused by improper string handling.
[1] https://github.com/mpv-player/mpv/commit/1e70e82baa9193f6f02...