Live data from Hacker News

My review of the C standard library in practice

nullprogram.com

71–80 of 94 posts

Re: My review of the C standard library in practice

#71

Earlier quoted context omitted.

> but libc needs to keep the same API The "needs to" is debatable. Compared to the C++ stdlib, the C stdlib is so small that adding a modernized and incompatible "v2" next to "v1" is realistic. The effort could start as a 3rd party implementation similar to MUSL. The old headers with the old APIs would still exist for "legacy code" but would generate "deprecated" warnings. Once that new "3rd party stdlib" has proven…

Would you keep the naming style?

No, at the very least I would add a (reserved) stdc_ prefix to all stdlib functions (and defines, and header filenames...), and keep options open for API versioning (e.g. stdc2_...).

That way we could also get rid of all the random reserved identifiers we need to (theoretically) adhere to now.

Re: My review of the C standard library in practice

#72

Earlier quoted context omitted.

So one should focus on creating versioned API IMO. Nobody does that for some reason. I should be able to declare my program to use v2023 API and consume libraries which use v1975 API.

That doesn't work in languages with nominal types, and even structurally-typed languages can have a hard time. You call a function that returns a v1975/SomeStruct and pass it to a function expecting a v2022/SomeStruct. What does the new function do when some of the struct fields are set to invalid values?

So my thoughts are as follows:

1. Every source file must be annotated with language version.

2. Every language version can remove things from previous language versions. More like "hide" I guess, but you can't compile source file that's using removed features or APIs. So to migrate to new version you're supposed to change sources. Of course preferable using some migration tools, but that's out of scope.

3. You can access old API indirectly. For example if you're using library which uses old API and returns struct from that old API.

4. There should be some well thought rules for situation that you describe. To make structs forward and backward compatible as much as possible. May be even to provide some implicitly running migrations to convert between structs with different versions.

5. If there's no way to automatically convert v1975/SomeStruct to v2022/SomeStruct, you can't do that and need to convert it manually.

This is hard problem and must be thought on every level: data layout compatibilities, ABI compatibilities, type system compatibilities. But I'm not convinced that it's unsolvable problem. And if solved it would provide great benefit allowing lots of freedom and agility for language development.

Re: My review of the C standard library in practice

#73
post #18

Earlier quoted context omitted.

You won't find that on either ISO C nor POSIX specifications. It only happens to be implemented that way in some environments, as a macro to either thread local or some function call that retrieves the right errno. Still, even that doesn't take care about signal handling.

That's a total strawman. Errno has nothing to do with signal handling at all. While errno is archaic, it is a completely sound design. That you need to be very careful in signal handlers is common knowledge, and obvious from what they do; signals handlers as a first approximation behave like executed in a separate thread, but even worse since they hijack a running thread and thus block the hijacked thread from execut…

You call it a strawman, I call it knowing what matters when writing portable code across multiple platforms and compilers.

You will notice that I have linked Open Group errno documentation in another post. That is what matters, not what GNU thinks.

Re: My review of the C standard library in practice

#74

Earlier quoted context omitted.

Would you keep the naming style?

No, at the very least I would add a (reserved) stdc_ prefix to all stdlib functions (and defines, and header filenames...), and keep options open for API versioning (e.g. stdc2_...). That way we could also get rid of all the random reserved identifiers we need to (theoretically) adhere to now.

That's a good start, at least for function names, not sure if I would use the same logic for everything.

In practice I find the prefix style better than namespaces, as a way to avoid information scattering.

Snake case would probably win the race, but I am not sure that many old-timers would be ready to let go the old habit of abbreviated function names.

After a while we don't see it for what it is, but I think that most of the naming is ugly, especially the string functions...

Re: My review of the C standard library in practice

#75

Earlier quoted context omitted.

> there's no way to import both the v1 and v2 APIs at the same time in your code, so you don't have a way to declare a conversion function. In Go major versions have different import paths. You can import multiple major versions of the same library: import ( http_server_old "example.com/http_server" http_server_new "example.com/http_server/v2" )

Sure, but that's not what's being discussed here. In C and C++, it's idiomatic to have different major versions use the same includeable name, with the version resolution handled by the build system.

... where "build system" means a bunch of preprocessor macros, at which point you just use the oldest version of everything available and never update anything that works.

Re: My review of the C standard library in practice

#76
post #38

Earlier quoted context omitted.

Still doesn't cover signals, only works if C11 threads are being used (it is all open if OS threads follow the same TLS mechanism), and those C89 and C99 code bases get nothing from it anyway.

In practice it works with major thread implementations even before C11, even though it doesn't say so in the standard. You can't call OS functions from a signal handler so I can't see why signals would matter. Have I missed something?

You can, it is UB in what might happen, you either get lucky, or not.

Don't expect a compiler error, this is C, where the programmer knows best.

Re: My review of the C standard library in practice

#77
post #62

Earlier quoted context omitted.

The last paragraph reads: > If you would like to see interesting innovation, check out what Cosmopolitan Libc is up to. It’s what I imagine C could be if it continued evolving along practical dimensions.

I found that sentence weird because when I checked Cosmopolitan, it included most if not all of the functions this article was claiming shouldn't be used.

That confused me, too. Cosmopolitan is an interesting piece of engineering, but its goals seem entirely orthogonal to the topic of this article.

Re: My review of the C standard library in practice

#78

> As with volatile, C is using the type system to indirectly achieve a goal. Types are not atomic, loads and stores are atomic I don't get this argument. This same wording can actually be used to make pointers untyped: "pointers are not inherently typed, loads and stores are typed. (In fact, LLVM IR recently made pointers untyped too)". And yet, clearly there's value in having them typed at the language level. Simila…

These type qualifiers cause the opposite foot gun. The programmer confidently writes compound assignments, which look like they're atomic/volatile thanks to the type qualifier but of course they are not. With intrinsics you don't have that problem. You just can't write the compound assignment - it doesn't exist, whereas in C it is just quietly compiled to two separate operations. C++ 20 deprecated this nonsense, but…

I'm not a big fan of restarting the discussion from the big Reddit argument on the same topic[1], but as I understand it: from embedded (or at least some developers') POV, this is a non-concern. Volatile never implied atomic in regards to interrupts, and pretending it should is wrong. On some platforms, single volatile load/store (whether `*ptr = 1234`, or `volatile_store(ptr, 1234)`) already can compile to "two separate operations" that can be interrupted in the middle. With that in mind, you are already supposed to be aware of that and execute such operations in no-interrupt contexts, and having this also apply to compound assignments is no more of a footgun than any other operation on such memory.

(and if you do not need to care about the above for your platform/use-case, then I don't see why you would care about whether compound assignment compiles to one, two or more discrete opcodes)

Not to mention, `REG |= 0x4` is just too entrenched (and seen as idiomatic) on some platforms.

[1] https://www.reddit.com/r/cpp/comments/jswz3z/compound_assign...

Re: My review of the C standard library in practice

#79
post #5

Earlier quoted context omitted.

Rust kind of does this. Language feature uses "edition" and has crates for its standard api instead of built in.

It really doesn't, though, although in principle Editions could provide an escape hatch it would be so drastic as to be largely unthinkable. Because of Rust's commitment to long term stability all of the standard library is there forever, even if it's a mistake and thus deprecated. std::u8::MAX will be in the library forever, even though you can write u8::MAX to get the same constant, name.trim_left_matches(remove) w…

> It really doesn't, though, although in principle Editions could provide an escape hatch it would be so drastic as to be largely unthinkable

Editions don't currently work for the std library, but I don't see why doing so would be drastic or unthinkable.

Re: My review of the C standard library in practice

#80

Earlier quoted context omitted.

> Easy to say with 50 years of hindsight. These are the pioneers we are talking about here. Eh, well kinda. Languages with decent string handling predate C, so it's not like there wasn't precedent to follow. The creators of C were pioneers, but they were also people who favoured a quick, hacky approach over a clean careful one. That has certain advantages, but it's rather unfortunate that C has become so foundational…

What would those languages be? I'm only used to relatively modern languages with "decent string handling", but they almost always treat strings as opaque high level objects with dynamic memory allocation under the hood. Such an approach wouldn't exactly fit into the C philosophy. Also, once UNICODE is added to the mix (which involves a lot more than just the text encoding), a decent string processing library isn't ex…

Pascal would be one example.

> with dynamic memory allocation under the hood

C strings are typically dynamically allocated, are they not?

Post reply on HN