My C23 Conventions
My C23 Conventions Oct 15, 2026 · Abner Coimbre This is one of a three-part mini series about how I’m using modern C as of late 2026. Before C23, I was taken in by How I Program C for years and years, and would even feel guilty for choosing C99 instead of -ansi -pedantic . But holy crap man I’m done being a doomsday prepper! These days I maintain a large-enough project with enough users where I could stand less foot-shooting. (I’ve also got bills to pay.) So here’s three modern C conventions which started to rise naturally from adding -std=c23 to my zig build: 1. Unused? Maybe? (Yes) Our first one is centered around [[nodiscard]] . If I draw an app window or panel I require the caller to interrogate the rect of the thing that was just rendered: [[nodiscard]] rect_t draw_screen( /* ... / ) { rect_t screen_r = { }; // Draw stuff // ... return screen_r; } It’s a business logic bug if my caller doesn’t capture the rectangle for later processing. Therefore [[nodiscard]] has been such a godsend for fixing bugs… Except there’s a choice few instances where I will draw something with no genuine use for the resulting rect: enum screen_opt popup_options = / ... / ; draw_screen(popup_options, / ... / ); // ERROR: damn you [[nodiscard]] Clang will error out here. I could capture the var and never use it but… rect_t rect = draw_screen( / ... / ); // ERROR: unused variable (I have -Werror enabled) Pre-C23 I could solve this through an UNUSED() macro: #ifndef UNUSED #define UNUSED(x) (void)(x) #endif rect_t rect = draw_screen( / ... */ ); UNUSED(rect); // Silences the compiler, but now I'm sad. But now with C23 I’ve deleted the macro in favor of [[maybe_unused]] : [[maybe_unused]] rect_t rect = draw_screen(...); // OK Phew I feel better. I’ve also noticed this is happening all over the place: lots of different routines returning different types that are marked with [[nodiscard]] . So for those few exceptions why not just go for: [[maybe_unused]] auto _ = foo(); // A new pattern emerges Bonus: For any macro fans out there a fellow indie dev (Zardium) offered an additional improvement. From our chat server: Zardium: #define _ [[maybe_unused]] auto CONCAT_MACRO(zz, COUNTER ) Abner: ahh, unique varname. nice one Zardium: you could take the auto out if you don’t want it to look so weird. then it acts exactly like the _ in other languages, where it can be redefined Abner: Yep totally. Although here I was celebrating my removal of a macro… XD 2. More Perfect Unions I’ve been cheated and lied to: designated initializers DO NOT clear my struct to zero and I can’t even trust {0} anymore. (Talk about a betrayal.) The problem is I’ll often have data types like this contrived timestamp: typedef struct { Type type; char buf[MAX]; union { time_t time; float dt; date_t date; }; } stamp_t; And if I partially initialize one: stamp_t stamp = { .type = STAMP_TYPE_BLA_BLA }; While it does mean we successfully zeroed out buf (thanks to empty initialization ), those union members are just a different beast. My dear reader: I’ve made the costly mistake of assuming date would clear to zero! As I Spongebob-panicked in public Lobsters member accelbread offered a sage revelation: accelbread: It used to be that GCC would fully zero the union anyways, and clang would only zero the time_t, though around a year ago they swapped behaviors iirc. Either way, standard only requires the time_t be zeroed and not whole union. GCC has a flag to always zero whole union. Also C23 initialization always inits whole union Wait WHAT? clang and GCC SWAPPED what now? I won’t investigate further. Everything about this is dangerous af unless we hug C23 tightly and never let go: stamp_t stamp = { }; stamp.type = STAMP_TYPE_BLA_BLA; Accelbread went on to say two more things: “Note the C23 zero init whole thing is nothing in the brackets, not 0 in brackets; { 0 } does same thing as before C23” “Also if you control union layout, putting largest member first is safest” Yessir. 3. Enums In C Were Magic (And I Don’t Like It) If I want to customize some dialog box I’ll use bit flags: enum { OPT_AUTOSCROLL = ( 1 << 0 ), OPT_NOCURSOR = ( 1 << 1 ), OPT_CENTERED = ( 1 << 2 ), OPT_NOCLOSE = ( 1 << 3 ), // ... }; [[nodiscard]] bool draw_warning( const char * msg, int opt); Even if we disagree with this approach lots of relevant libraries or projects do it: _ = draw_warning( "you done messed up A-A-RON" , OPT_CENTERED | OPT_NOCLOSE); Of course, erroneous bit shifting will land anyone into trouble and yield comical numbers. Thankfully C enums are a plain int underneath so the compiler will warn us if we defined a bad constant inside the enum that won’t fit. :) I’m kidding. Sigh. Well, I’m growing fearful of sage revelations but this one comes from Modern C by Jens Gustedt: Jens: If enumeration constants do not fit into signed int, if possible, the enumeration type is adjusted such that it can store all enumeration constants. Noooooo! Jens: The property that an an enumeration type is adjusted such that it fits all its constants could have surprising effects sobs Jens: So, it is preferable to specify the underlying integer type explicitly THERE IS HOPE? enum options : int { OPT_AUTOSCROLL = ( 1 << 0 ), OPT_NOCURSOR = ( 1 << 1 ), OPT_CENTERED = ( 1 << 2 ), OPT_NOCLOSE = ( 1 << 3 ), // ... }; It compiles! We shoot. We score. We stick with C23. Epilogue In part two of our mini series I’ll discuss three more conventions that I’ve fleshed out thanks to modern C: starting with safer macro usage (forgive me Zig folks.) P.S. If you want to shape this discussion maybe join the Handmade Cities server! -Abner c23 Are We Harold Bloom? →
Comments
Do you know how you can recognize a real C developer? They are not lazy, that's their main feature. They're patient and have stamina. I will check it, but please fucking update the formatting.
Besides that, welcome to our wholesome community where the real devs live. Half of members tried to hack the platform. And that's awesome.
...sounds like an [ad|bot]... || maybe I'm being paranoid. Who knows?
You are not alone.
It is not.
I shall remain skeptical.
I swear with my hand on the bible. Some people are just like that.