←back to thread

The C23 edition of Modern C

(gustedt.wordpress.com)
515 points bwidlar | 4 comments | | HN request time: 0.644s | source
Show context
johnisgood ◴[] No.41854897[source]
Personally this[1] just makes C much more complicated for me, and I choose C when I want simplicity. If I want complicated, I would just pick C++ which I typically would never want. I would just pick Go (or Elixir if I want a server).

"_BitInt(N)" is also ugly, reminds me of "_Bool" which is thankfully "bool" now.

[1] guard, defer, auto, constexpr, nullptr (what is wrong with NULL?), etc. On top of that "constexpr" and "nullptr" just reeks of C++.

That said, Modern C is an incredible book, I have been using it for C99 (which I intend to continue sticking to).

replies(10): >>41854987 #>>41855182 #>>41855214 #>>41855526 #>>41855553 #>>41856362 #>>41857239 #>>41859032 #>>41860073 #>>41935596 #
consteval ◴[] No.41860073[source]
> If I want complicated, I would just pick C++ which I typically would never want

In my opinion, complexity doesn't scale linearly like this. Sometimes, in fact often times, having more complex tools means a simpler process and end result.

It's like building a house. A hammer and screwdriver are very simple. A crane is extremely complex. But which simplifies building a house? A crane. If I wanted to build a house with only a hammer and screwdriver, I would have to devise incredibly complex processes to get it done.

You see the same type of thing in programming languages. Making a generic container in C++ is trivial. It's very, very hard in C. You can make it kind of generic. You can use void * and do a bunch of manual casting. But it's cumbersome, error prone, and the code is more complex. It's counter-intuitive - how can C, a simpler language, produce code that is more complex than C++?

Or look at std::sort vs qsort. The power of templates and functors makes the implementation much simpler - and faster! We don't have to pass around void * and dereference them at runtime, instead we can build in comparison into the definition of the function itself. No redirection, no passing on the stack, and we can even go so far as to inline the comparison function.

There's really lots of examples of this kind of stuff. Point being, language complexity does not imply implementation complexity.

replies(1): >>41864908 #
1. swells34 ◴[] No.41864908[source]
In my experience, complex tools encourage fluffy programming. You mention a generic container; if I were using C, I just wouldn't use a generic container; instead, I'd specify a few container types that handle what needs handled. If there seem to be too many types, then I immediately start thinking that I'm going down a bad architecture path, using too many, or too mixed, abstraction layers, or that I haven't broken down the problem correctly or fully.

The constraints of the tool are inherited in the program; if the constraints encourage better design, then the program will have a better design. You benefit from the language providing a path of least resistance that forces intentionality. That intentionality makes the code easier to reason about, and less likely to contain bugs.

You do pay for this by writing more boilerplate, and by occasionally having to do some dirty things with void pointers; but these will be the exception to the rule, and you'll focus on them more since they are so odd.

replies(2): >>41870621 #>>41898956 #
2. consteval ◴[] No.41870621[source]
Sometimes, but I would argue that C is too simplistic and is missing various common-sense tools. It's definitely improving, but with things like namespaces there's pretty much no risk of "too complex" stuff.

Also, I wouldn't be saying this if people didn't constantly try to recreate C++-isms in C. Which sometimes you need to do. So, then you have this strange amalgamation that kind of works but is super error prone and manual.

I also don't necessarily agree that C's constraints encourage better design. The design pushes far too much to runtime, which is poor design from a reasoning point of view. It's very difficult to reason about code when even simple data models require too much indirection. Also, the severely gimped type system means that you can do things you shouldn't be able to do. You can't properly encode type constraints into your types, so you then have to do more validation at runtime. This is also slightly improving, starting with _Bool years ago.

C++ definitely is a very flawed language with so, so many holes in its design. But the systems it has in place allows the programmer to more focus on the logic and design of their programs, and less on just trying to represent what they want to represent. And templates, as annoying as the errors are, prevent A LOT of runtime errors. Remember, every time you see a template that translates into pointers and runtime checks in C.

3. avvvv ◴[] No.41898956[source]
I think that is fair. A simple language with a simple memory model is nice to work with.

I also think that it wouldn't be bad for code to be more generic. It is somewhat unnecessary for a procedure to allow an argument of type A but not of type B if the types A and B share all the commonalities necessitated by the procedure. Of course procedures with equivalent source code generate different machine code for different types A or B, but not in a way that matters much.

I believe it is beneficial for the language to see code as the description of a procedure, and to permit this description to be reused as much as possible, for the widest variety of types possible. The lack of this ability I think might be the biggest criticism I have for C from a modern standpoint.

replies(1): >>41899090 #
4. Gibbon1 ◴[] No.41899090[source]
I feel that if C had tagged unions and a little sugar you could write non magical generic functions in C. Non magical meaning unlike C++ etc instead of the compiler selecting the correct function based on the arguments the function itself can tell and handle each case.

Basically you can write a function that takes a tagged union and the compiler will passed the correct union based on named arguments.

   int ret = foo(.slice = name);

   int ret = foo(.src = str, .sz = strlen(str));