Back

The C23 edition of Modern C

348 points13 hoursgustedt.wordpress.com
johnisgood3 hours ago

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).

nickelpro38 minutes ago

> what is wrong with NULL?

One of the few advantages of ISO standardization is you can just read the associated papers to answer questions like this: https://wg21.link/p2312

The quick bullet points:

* Surprises when invoking a type-generic macro with a NULL argument.

* Conditional expressions such as (1 ? 0 : NULL) and (1 ? 1 : NULL) have different status depending how NULL is defined

* A NULL argument that is passed to a va_arg function that expects a pointer can have severe consequences. On many architectures nowadays int and void* have different size, and so if NULL is just 0, a wrongly sized argument is passed to the function.

mmphosis2 hours ago

NULL is not wrong. The things that I will do with NULL are

cornstalks3 hours ago

> what is wrong with NULL?

For starters, you have to #include a header to use it.

zik2 hours ago

And it avoids the NULL == 0 ambiguity, allowing for better type checking.

musicale1 hour ago
belter11 hours ago

Important reminder just in the Preface :-)

Takeaway #1: "C and C++ are different: don’t mix them, and don’t mix them up"

jasode10 hours ago

>Takeaway #1: "C and C++ are different: don’t mix them, and don’t mix them up"

Where "mixing C/C++" is helpful:

- I "mix C in with my C++" projects because "sqlite3.c" and ffmpeg source code is written C. C++ was designed to interoperate with C code. C++ code can seamlessly add #include "sqlite3.h" unchanged.

- For my own code, I take advantage of "C++ being _mostly_ a superset of C" such as using old-style C printf in C++ instead of newer C++ cout.

Where the "C is a totally different language from C++" perspective is helpful:

- knowing that compilers can compile code in "C" or "C++" mode which has ramifications for name mangling which leads to "LINK unresolved symbol" errors.

- knowing that C99 C23 has many exceptions to "C++ is a superset of C" : https://en.wikipedia.org/wiki/Compatibility_of_C_and_C%2B%2B...

tialaramex10 hours ago

The entire I/O streams (where std::cout comes from) feature is garbage, if this was an independent development there is no way that WG21 would have taken it, the reason it's in C++ 98 and thus still here today is that it's Bjarne's baby. The reason not to take it is that it's contradictory to the "Don't use operator overloading for unrelated operations" core idea. Bjarne will insist that "actually" these operators somehow always meant streaming I/O but his evidence is basically the same library feature he's trying to justify. No other language does this, and it's not because they can't it's because it was a bad idea when it was created, it was still a bad idea in 1998, the only difference today is that C++ has a replacement.

The modern fmt-inspired std::print and std::println etc. are much nicer, preserving all the type checking but losing terrible ideas like stored format state, and localisation by default. The biggest problem is that today C++ doesn't have a way to implement this for your own types easily, Barry illustrates a comfortable way this could work in C++ 26 via reflection which on that issue closes the gap with Rust's #[derive(Debug)].

spacechild15 hours ago

Remember that C++ originally didn't have variadic templates, so something like std::format would have been impossible back in the day. Back in the day, std::iostream was a very neat solution for type safe string formatting. As you conceded, it also makes it very easy to integrate your own types. It was a big improvement over printf(). Historic perspective is everything.

dieortin8 hours ago

> The biggest problem is that today C++ doesn't have a way to implement this for your own types easily

I’m not sure about the stdlib version, but with fmtlib you can easily implement formatters for your own types. https://fmt.dev/11.0/api/#formatting-user-defined-types

tialaramex7 hours ago

I think the problem is that your idea of "easy" is "Here's a whole bunch of C++ you could write by hand for each type" while the comparison was very literally #[derive(Debug)]. I wasn't abbreviating or referring to something else, that's literally what Rust programmers type to indicate that their type should have the obvious boilerplate implementation for this feature, in most types you're deriving other traits already, so the extra work is literally typing out the word Debug.

pjmlp9 hours ago

Perfectly iostreams happy user since 1993.

+3
Dwedit8 hours ago
codr78 hours ago

Same, as long as I stay the hell away from locales/facets.

Type safe input/output stream types and memory backed streams served on a silver plate is a pretty decent improvement over C.

fra4 hours ago

This was a tip my hatn excellent to you

johnisgood3 hours ago

Why?

+1
einpoklum7 hours ago
tightbookkeeper7 hours ago

Yep, it’s very clean once you get the hang of it.

throwaway20372 hours ago

Over the years, I have heard numerous complaints about C++ I/O streams. Is there a better open source replacement? Or do you recommend to use C functions for I/O?

wakawaka285 hours ago

>No other language does this, and it's not because they can't it's because it was a bad idea when it was created, it was still a bad idea in 1998, the only difference today is that C++ has a replacement.

Hindsight is 20/20, remember that. Streams are not that bad of an idea and have been working fine for decades. You haven't named a problem with it other than the fact the operators are used for other stuff in other contexts. But operator overloading is a feature of C++ so most operators, even the comma operator, can be something other than what you expect.

>The biggest problem is that today C++ doesn't have a way to implement this for your own types easily, Barry illustrates a comfortable way this could work in C++ 26 via reflection which on that issue closes the gap with Rust's #[derive(Debug)].

You can trivially implement input and output for your own types with streams.

You appear to be a Rust guy whose motive is to throw shade on C++ for things that are utterly banal and subjective issues.

PaulDavisThe1st1 hour ago

What they mean is this:

     struct Foo {
       int a;
       float b;
       std::string c;
     };


     Foo foo;
     std::cout << foo;
with no extra code. It's called reflection, where the compiler can generate good-enough code to generate a character-stream serialization of an object without any human intervention.
tightbookkeeper7 hours ago

What’s wrong with it?

lugu9 hours ago

Thank you.

accelbred10 hours ago

C++ can seamlessly include C89 headers.

The C library headers for libraries I write often include C11/C99 stuff that is invalid in C++.

Even when they are in C89, they are often incorrect to include without the include being in an `extern "C"`.

nuancebydefault10 hours ago

Extern "C" around the prototypes is mandatory, otherwise your linker will search for C++ symbols, which cannot be found in the C libraries you pass it.

Conscat10 hours ago

Clang supports C11 - 23 in C++, as well as some future C features like fixed-point integers. The main pain points with Clang are just the fundamental differences like void* and char, which don't typically matter much at an interoperability layer.

kccqzy9 hours ago

Yeah plenty of headers first have `#ifdef __cplusplus` and then they add `extern "C"`. And of course even then they have to avoid doing things unacceptable in C++ such as using "new" as the name of a variable.

It takes a little bit of an effort to make a header work on C and C++. A lot less effort than making a single Python file work with Python 2 and 3.

Someone9 hours ago

> C++ code can seamlessly add #include "sqlite3.h" unchanged.

Almost seamlessly. You have to do

  extern “C” {
    #include "sqlite3.h"
  }
(https://isocpp.org/wiki/faq/mixing-c-and-cpp#include-c-hdrs-...)
cornstalks8 hours ago

If we're nitpicking then sqlite3.h already has `#ifdef __cplusplus` and `extern "C" {`. So yes, from the user's perspective it is seamless. They do not need to play the `extern "C" {` game.

MathMonkeyMan8 hours ago

My brief foray into microcontroller land has taught me that C and C++ are very much mixed.

It's telling that every compiler toolchain that compiles C++ also compiles C (for some definition of "C"). With compiler flags, GCC extensions, and libraries that are kinda-sorta compatible with both languages, there's no being strict about it.

_My_ code might be strict about it, but what about tinyusb? Eventually you'll have to work with a library that chokes on `--pedantic`, because much (most?) code is not written to a strict C or C++ standard, but is "C/C++" and various extensions.

pjmlp11 hours ago

Specially relevant to all those folks that insist on "Coding C with a C++ compiler", instead of safer language constructs, and standard library alternatives provided by C++ during the last decades.

flohofwoe11 hours ago

Funny because for a long time the Microsoft MSVC team explicitly recommended compiling C code with a C++ compiler because they couldn't be arsed to update their C frontend for over two decades (which thankfully has changed now) ;)

https://herbsutter.com/2012/05/03/reader-qa-what-about-vc-an...

rdtsc10 hours ago

That thing always baffled me, this huge company building a professional IDE couldn't figure out how to ship updates to the C compiler.

> it is hard to say no to you, and I’m sorry to say it. But we have to choose a focus, and our focus is to implement (the standard) and innovate (with extensions like everyone but which we also contribute for potential standardization) in C++.

I mean, yeah if it came from a two member team at a startup, sure focus on C++, understandably. But Microsoft, what happened to "Developers! Developers! Developers!"?

Jtsummers10 hours ago

It's not baffling, it's remarkably consistent. They implemented Java as J++ and made their version incompatible in various ways with the standard so it was harder to port your code away from J++ (and later J#). They implemented things in the CSS spec almost exactly opposite the specification to lock people into IE (the dominant browser, if you have to make your site work with 2+ incompatible systems which will you focus on?). Not supporting C effectively with their tools pushed developers towards their C++ implementation, creating more lock-in opportunities.

+1
pjmlp9 hours ago
AlotOfReading10 hours ago

Funnily enough, the intellisense parser does support C syntax because it's using a commercial frontend by edison under the hood. MSVC's frontend doesn't.

pjmlp9 hours ago

Yeah, 12 years ago, when governments couldn't care less about nation state cyberattacks, and Microsoft was yet to be called by the Congress to testify on their failures.

com2kid11 hours ago

Perfectly valid to do if you need to interface with a large C code base and you just want to do some simple OO here and there. Especially if you cannot have runtime exceptions and the like.

This is how I managed to sneak C++ into an embedded C codebase. We even created some templates for data structures that supported static allocation at compile time.

f1shy11 hours ago

What would be an example of "simple OO here and there" that cannot be done cleanly in plain C?

bobmcnamara11 hours ago

Templating on pixel classes so that a blitter builds all supported pixel paths separately and inlines them.

Yes you can do it less cleanly with macros or inline functions. But you can't do it performantly with struct and function pointers.

+1
com2kid5 hours ago
+2
raluk11 hours ago
+1
adamrezich11 hours ago
cozzyd10 hours ago

CRTP?

pjmlp9 hours ago

Yeah, but one should provide C++ type safe abstractions on top.

Just like one doesn't use Typescript to keep writing plain old JavaScript, then why bother.

Spivak11 hours ago

I mean as long as your goal is specifically to do that I think it's fine. Using a C++ compiler to compile a C program isn't that rare.

f1shy11 hours ago

A couple of months ago, in the company I work, there was a talk from HR, where they explained how to make a good CV (the company is firing lots of people). She say: "if you have experience in programming C, you can writing just that, or, if you have lots of experience in C, is customary to write ``C++ Experience'' "

Sooo... yeah... I should definitely change company!

kstrauser11 hours ago

That literally made me do a spit take, and it was fizzy water and it burned.

My god. That's amazing.

thenipper8 hours ago

How many pluses until you should just say you have D experience?

jpcfl11 hours ago

Bjarne should have called it ++C.

wnoise5 hours ago

Nah. It's just the natural semantics -- he added stuff to C, but returned something that wasn't actually more advanced...

card_zero11 hours ago

Because people choose to use pre-increment by default instead of post-increment?

Why is that?

int_19h10 hours ago

It should be ++C because with C++ the value you get from the expression is the old one.

If you're asking why people use pre-increment by default instead of post-increment, it's mostly historical. The early C compilers on resource-constrained platforms such as early DOS were not good at optimization; on those, pre-increment would be reliably translated to a simple ADD or INC, whereas code for post-increment might generate an extra copy even if it wasn't actually used.

For C++ this was even worse with iterators, because now it depended on the compiler's ability to inline its implementation of postfix ++, and then prove that all the copies produced by that implementation have no side effects to optimize it to the same degree as prefix ++ could. Depending on the type of the underlying value, this may not even be possible in general.

The other reason is that all other unary operators in C are prefix rather than postfix, and mixing unary prefix with unary postfix in a single expression produces code that is easy to misunderstand. E.g. *p++ is *(p++), not (*p)++, even though the latter feels more natural, reading it left-to-right as usual. OTOH *++p vs ++*p is unambiguous.

card_zero10 hours ago

K&R seems to use pre-increment early on, then post-increment consistently (or a lot, anyway, I haven't done a thorough check) after chapter 3, in situations where either would do. In fact, after introducing post-increment at 2.8.

jpcfl4 hours ago

> It should be ++C because with C++ the value you get from the expression is the old one.

You get it!

wpollock7 hours ago

The PDP-11 that C originally targeted had address modes to support the stack. Pre-increment and post-decrement therefore did not require a separate instruction; they were free. After the PDP-11 went the way of the dodo, both forms took a machine cycle so it (mostly) became a stylistic issue. (The two operators have different semantics, but the trend to avoid side-effects in expressions means that both are most often used in a single expression statement like "++x;" or "x++;", so it comes down to your preferred style.)

zabzonk2 hours ago

Please explain what you mean by "a separate instruction".

jejdjdbd11 hours ago

Why would you use post increment by default? The semantics are very particular.

Only on very rare occasions I need post increment semantics.

And in those cases I prefer to use a temporary to make the intent more clear

+4
card_zero11 hours ago
codr78 hours ago

If you're used to the idiom, the intent couldn't be clearer.

I miss it when switching between C/++ and other languages.

tialaramex10 hours ago

Why use this operator? Like most C and C++ features the main reason tends to be showing off, you learned a thing (in this case that there are four extra operators here) and so you show off by using it even if it doesn't make the software easier to understand.

This is not one of those beginner -> journeyman -> expert cycles where coincidentally the way you wrote it as a beginner is identical to how an expert writes it but for a very different reason. I'd expect experts are very comfortable writing either { x = k; k += 1; } or { k += 1; x = k; } depending on which they meant and don't feel an itch to re-write these as { x = k++; } and { x = ++k; } respectively.

I'm slightly surprised none of the joke languages add equally frivolous operators. a%% to set a to the remainder after dividing a by 10, or b** to set b as two to the power b or some other silliness.

trealira4 hours ago

They can be useful when adding things to an array in a loop. A trivial example which removes a character from a null terminated string:

  void remove_char(char *s, char c) {
    size_t i, j;

    for (i = j = 0; s[i] != '\0'; i++)
      if (s[i] != c)
        s[j++] = c;
    s[j] = '\0';
  }

This might be better expressed with a higher order filter function, but C is too low level for things like that.

There are also idioms for stack manipulation using them: "stack[sp++] = pushed" and "popped = stack[--sp]".

C code does a lot of incrementing and decrementing by one, and so having dedicated syntax for it is convenient.

+1
layer810 hours ago
cozzyd10 hours ago

It's more useful for pointers than for values, IMO

israrkhan2 hours ago

Most important aspect of C is its portability. From small microcontrollers to almost any computing platform. I doubt that any new version of C will see that much adoption.

If I want to live on cutting edge I would rather use C++2x or Rust rather than C.

Am I missing something? What benefit this supposedly modern C offers?

doe_eyes31 minutes ago

Of course they will, just like they did in the past with C11, GNU extensions, or some of the individual features that are now rolled into C23. For example, the 0b notation for binary numbers is widely used in the MCU world.

The microcontroller toolchains are generally built on top of GCC, so they get the features for free. There are some proprietary C compilers that are chronically lagging behind, but they are not nearly as important as they used to be two decades ago.

vitaminka1 hour ago

these features will eventually trickle down into the mainstream, kind of like C11 is doing at the moment

also, unless you're targeting embedded or a very wide set of architectures, there's no reason why you couldn't start using C23 today

zkirill11 hours ago

I was going to ask if there is a good list of C books and then answered my own question. It categorizes _Modern C_ as Intermediate level.

https://stackoverflow.com/questions/562303/the-definitive-c-...

emmanueloga_6 hours ago

Note that this is not a complete list, fwiw. For example, I doesn't include "Effective C." [1].

I like "Effective C" over "Modern C" because it's more engaging ... "Modern C" is super rigorous and feels a bit like reading an annotated spec of the language, which is what an expert may need, but makes for a dull read for a casual C user like me.

--

1: https://nostarch.com/effective-c-2nd-edition

auggierose11 hours ago

Table of contents in the sidebar doesn't work properly for me when I click on an entry (in macOS Preview).

bwidlar11 hours ago

I just test some links in the table of content, works fine for me. Using zathura pdf reader.

Jtsummers11 hours ago

Also works in Adobe and Firefox, but doesn't work in Chrome and Edge.

f1shy11 hours ago

Doesn't work for me either... but I will not dismiss the book because of that.

channel_t11 hours ago

Table of contents is definitely broken right now.

soegaard11 hours ago

Same here.

enriquto11 hours ago

So happy that we still get the dinosaur mascots! This is a good book.

bitbasher11 hours ago

Really looking forward to #embed, once the compilers catch up. Until then, Golang.

enriquto11 hours ago

This is not how C standards work. If it appears in the standard, it means that it is already implemented in some compilers (in that case, at least in gcc and clang).

pjmlp9 hours ago

That isn't really how it goes, that is how it used to be up to C99.

enriquto9 hours ago

Thanks for the correction! Do you know if there is a document from the standards body explaining the change in philosophy?

JonChesterfield7 hours ago

It's a nuisance to implement the thing you want to add to the standard yourself. It's easier to ship it in the language and then complain at compiler devs that they're running behind the edge of progress.

This interacts in the obvious way with refusing to correct mistakes after the fact for fear of breaking user code.

I don't believe anyone has written a paper along the lines of "let's not bother with the existing practice part anymore", it's more an emergent feature of people following local incentive structures.

MathMonkeyMan8 hours ago

I've heard something along the lines of "the standard is to define facilities that will be used in most programs, and to codify widespread existing practice." That was in the context of "I don't like this proposed feature," though. This was for C++, not C.

A lot of stuff in the C++11 standard library was based on widespread use of Boost. Since then, I don't know. Also, were things like templates and lambdas implemented as compiler extensions before standardization? I don't know, but I doubt it. Maybe "we're a committee of people who will decide on a thing and we hope you like it" was always the norm in many ways.

f1shy11 hours ago
jpcfl11 hours ago

Or

    xxd --include <file>
:)
Keyframe9 hours ago

The anti-Rust approach!

accelbred10 hours ago

I end up using a .S asm file with .incbin directives to embed files.

#embed would be much nicer

JonChesterfield7 hours ago

Incbin works just fine from inline asm fwiw

rfl89011 hours ago

Clang 19 has it.

kristianp5 hours ago

GCC support has been around since gcc 11 apparently. See table at (1). This is available in ubuntu 22.04. The page below also shows support for C26!

1) https://gcc.gnu.org/projects/cxx-status.html#:~:text=C%2B%2B...

russellbeattie8 hours ago

Wow, the use of attributes like [[__unsequenced__]], [[maybe_unused]] and [[noreturn]] throughout the book is really awful. It seems pretty pedantic of the author to litter all the code examples with something that is mostly optional. For a second I wondered if C23 required them.

amomchilov5 hours ago

Such is the issue with bad defaults. Opting into the sensible thing makes most of your code ugly, instead of just the exceptions.

leonheld8 hours ago

One of my favorite books ever.

nimish8 hours ago

My kingdom for fully specified, well defined portable bitfields.

jhatemyjob5 hours ago

Can someone link me to an article that explains why C is basically frozen at C99 for all practical purposes? Few projects worth talking about leverage features from C11 and newer

pornel2 hours ago

C99 is still new! Microsoft tried to kill C by refusing to implement anything that wasn't also in C++. MSVC was 16 years late implementing C99, and implemented only the bare minimum. Their C11 implementation is only 11 years late.

I suspect that decades of C being effectively frozen have caused the userbase to self-select to people who like C exactly the way it is (was), and don't mind supporting ancient junk compilers.

Everyone who lost patience, or wanted a 21st century language, has left for C++/Rust/Zig or something else.

jumpman_miya11 hours ago

in example 1.1 i read that as 'tits_square' until i saw the output

glass-z1311 hours ago

that's by design

ralphc10 hours ago

How does "Modern" C compare safety-wise to Rust or Zig?

WalterBright10 hours ago

Modern C still promptly decays an array to a pointer, so no array bounds checking is possible.

D does not decay arrays, so D has array bounds checking.

Note that array overflow bugs are consistently the #1 problem with shipped C code, by a wide margin.

layer89 hours ago

> no array bounds checking is possible.

This isn’t strictly true, a C implementation is allowed to associate memory-range (or more generally, pointer provenance) metadata with a pointer.

The DeathStation 9000 features a conforming C implementation which is known to catch all array bounds violations. ;)

trealira4 hours ago

> The DeathStation 9000 features a conforming C implementation which is known to catch all array bounds violations. ;)

That actually really does exist already with CHERI CPUs, whose pointers are tagged with "capabilities," which catch buffer overruns at runtime.

https://tratt.net/laurie/blog/2023/two_stories_for_what_is_c...

https://msrc.microsoft.com/blog/2022/01/an_armful_of_cheris/

uecker9 hours ago

Right. Also it might it sound like array-to-pointer decay is forced onto the programmer. Instead, you can take the address of an array just fine without letting it decay. The type then preserves the length.

WalterBright5 hours ago

C: int foo(int a[]) { return a[5]; }

    int main() {
        int a[3];
        return foo(a);
    }

    > gcc test.c
    > ./a.out
Oops.

D: int foo(int[] a) { return a[5]; }

    int main() {
        int[3] a;
        return foo(a);
    }

    > ./cc array.d
    > ./array
    core.exception.ArrayIndexError@array.d(1): index [5] is out of bounds for array of length 3
Ah, Nirvana!

How to fix it for C:

https://www.digitalmars.com/articles/C-biggest-mistake.html

codr78 hours ago

Nice, when you know the length at compile time, which is rarely from my experience.

The holy grail is runtime access to the length, which means an array would have to be backed by something more elaborate.

TZubiri8 hours ago

"The DeathStation 9000"

The what now?

bsder6 hours ago

Nasal daemons for those of us of a slightly older vintage ...

+1
layer88 hours ago
renox10 hours ago

You'd be surprised: Zig has one UB (Undefined Behaviour) that C doesn't have!

In release fast mode, unsigned overflow/underflow is undefined in Zig whereas in C it wraps.

:-)

Of course C has many UBs that Zig doesn't have, so C is far less safe than Zig, especially since you can use ReleaseSafe in Zig..

uecker9 hours ago

UB is does not automatically make things unsafe. You can have a compiler that implements safe defaults for most UB, and then it is not unsafe.

duped8 hours ago

That's implementation defined behavior, not undefined behavior. Undefined behavior explicitly refers to something the compiler does not provide a definition for, including "safe defaults."

Maxatar4 hours ago

The C standard says, and I quote:

>Possible undefined behavior ranges from ignoring the situation completely with unpredictable results ... or program execution in a documented manner characteristic of the environment (with or without the issuance of a diagnostic message)

So a compiler is absolutely welcome to make undefined behavior safe. In fact every compiler I know of, such as GCC, clang, MSVC has flags to make various undefined behavior safe, such as signed integer overflow, type punning, casting function pointers to void pointers.

The Linux kernel is notorious for leveraging undefined behavior in C for which GCC guarantees specific and well defined behavior.

It looks like there is also the notion of unspecified behavior, which gives compilers a choice about the behavior and does not require compilers to document that choice or even choose consistently.

And finally there is what you bring up, which is implementation defined behavior which is defined as a subset of unspecified behavior in which compilers must document the choice.

fuhsnn8 hours ago

Compilers are not prohibited to provide their own definition for UB, that's how UBsan exists.

renox7 hours ago

Well Zig has ReleaseSafe for this.. ReleaseFast is for using these UBs to generate the fastest code.

ahoka9 hours ago

By definition UB cannot be safe.

Maxatar3 hours ago

The definition given by the C standard allows for safe undefined behavior.

marssaxman8 hours ago

this depends on your choice of definition for "safe"

secondcoming9 hours ago

Does C automatically wrap? I thought you need to pass `-fwrapv` to the compiler to ensure that.

greyw8 hours ago

Unsigned overflow wraps. Signed overflow is undefined behavior.

+1
kbolino8 hours ago
renox8 hours ago

-fwrapv is for signed integer overflow not unsigned.

sp1rit8 hours ago

Yes, as unsigned overflow is fine by default. AFAIK the issue was originally that there were still machines that used ones complement for describing negative integers instead of the now customary twos complement.

jandrese9 hours ago

Modern C is barely any different than older C. The language committee for C is extremely conservative, changes tend to happen only around the edges.

einpoklum7 hours ago

It's only been a few years since I've come to feel I can rely on C compilers all supporting C99, for a library I'm maintaing [1]. And after a couple of years, sure enough - I get an issue opened asking for C89 compatibility because of some arcane embedded toolchain or what-not.

So, C23? ... that's nice and all, but, let's talk about it in 20 years or so T_T

[1]: https://github.com/eyalroz/printf

zerr10 hours ago

Do they still use 0-terminated strings/char* as the main string type?

Is the usage of single linked lists still prevalent as the main container type?

racingmars10 hours ago

> Do they still use 0-terminated strings/char* as the main string type?

Of course, it's still C.

> Is the usage of single linked lists still prevalent as the main container type?

As far as I can remember, the C standard library has never had any functions that used linked lists. Nor are there any container types, linked lists or otherwise, provided by C. So I'd say this is a question about how people teach and use C, not related to the language -- or language spec version -- itself.

eps8 hours ago

Don't feed the trolls.

EasyMark5 hours ago

I don’t think that will ever change. Will the possibly introduce a more modern string2 type? Maybe but it will probably be unlikely before 2050

codr78 hours ago

Embedded linked lists are pretty cool though.

KerrAvon8 hours ago

The C standard library provides no recognizable container types, so there's no "main" anything.