The C Standards Landscape: C89 to C23¶
The C language keeps evolving. Slowly, deliberately, and with a strong commitment to backward compatibility — but it evolves. You cannot write “modern C” without knowing which features came in which standard, because the answer to “can I use X?” always depends on what version your target compiler supports and what platform you’re building for. This file gives you the timeline, the milestones, and the practical answer to “what do I target in 2026?”
The Timeline¶
Standard |
Year |
Common name |
Notes |
|---|---|---|---|
C89 / C90 |
1989/90 |
ANSI C, K&R2 era |
The one every embedded shop still ships. Function prototypes, |
C95 |
1995 |
Amendment 1 |
Digraphs, |
C99 |
1999 |
Real modern C |
|
C11 |
2011 |
Threads-and-atomics C |
|
C17 |
2018 |
C11 + defect reports |
No new features. Bugfix release. |
C23 |
2024 |
The modern reset |
|
What Actually Changed That Matters for You¶
C99 — the “you must know this” baseline¶
//comments. Trivial, ubiquitous.Mixed declarations. Declare variables at first use, not just at block top.
<stdint.h>/<inttypes.h>. Exact-width integer types. You saw why in 01_types_and_integer_promotion.md.<stdbool.h>.bool,true,falseas macros. C23 promotes them to keywords.Designated initializers.
struct Point p = { .x = 1, .y = 2 };. Enormously useful; prevents bugs when struct fields get reordered.Compound literals.
f((int[]){1,2,3}, 3);. Inline array/struct construction.Variable-length arrays (VLAs). Made optional in C11. Do not use.
inlinekeyword. Compiler hint (weaker than it sounds; the real inlining decision is up to the optimizer).long longguaranteed 64 bits.restrictqualifier. Promise to the compiler that a pointer doesn’t alias anything else in scope. Enables vectorization; use carefully.
C11 — threads, atomics, and generic programming¶
<threads.h>. Standard threading (mutex, cnd, thrd). Half-adopted — glibc supports it, macOS/BSD Clang did not for years. In 2026 support is broader but still uneven. Use POSIX pthreads for real projects; know<threads.h>exists.<stdatomic.h>. Atomic types and operations. Well-supported everywhere by 2026. Use for lock-free counters and simple flags. Full memory-model literacy comes in Phase 4._Generic. Compile-time type dispatch. Enables typed macros:#define ABS(x) _Generic((x), \ int: abs, long: labs, double: fabs, float: fabsf)(x)
_Static_assert(cond, msg). Compile-time check._Static_assert(sizeof(int) == 4, "32-bit int required");.Anonymous struct/union members. Covered in 03_structs_unions_bitfields.md.
aligned_alloc(align, size). Aligned heap allocation. Alternative on macOS:posix_memalign.Annex K “bounds-checking”
_sfunctions. Skip. Optional annex, poorly implemented, universally disliked. Usesnprintfandstrlcpyinstead.
C17 — the boring good one¶
No new features. C11 with defect reports resolved. When someone says “target C17” they mean “target C11 as it was actually meant to be spec’d.” It’s the current default for GCC 11+ and Clang 11+. This is what you target for the roadmap.
C23 — the modern reset¶
C23 landed in 2024 as ISO/IEC 9899:2024. It’s the biggest cleanup since C99. Highlights that will affect how you write code:
bool,true,falseare keywords. No more<stdbool.h>needed.nullptrkeyword. Type-safe null pointer constant. Fixes decades ofNULL-is-sometimes-0-sometimes-(void*)0confusion. Works in variadic functions whereNULLdoesn’t.static_assertas a keyword._Static_assertretained as alias.Binary literals:
0b1010. Finally.Digit separators:
1'000'000. Same as C++.typeof(expr)andtypeof_unqual(expr). Standardizes what GCC has had for 30 years.constexpr. True compile-time constants.constexpr int MAX = 42;. Not as powerful as C++’s, but useful.[[attribute]]syntax. Standardized replacement for__attribute__((...)). Includes[[nodiscard]],[[deprecated("msg")]],[[maybe_unused]],[[fallthrough]],[[noreturn]]._BitInt(N). Precise-width integer of N bits, up to compiler limit. Useful for cryptography, hardware descriptions.#embed "file.bin". Include binary data directly. Ends thexxd -iera.<stdckdint.h>.ckd_add,ckd_sub,ckd_mul— checked overflow arithmetic in the standard library.enumwith explicit underlying type.enum E : uint8_t { A, B, C };.autofor type inference in variable declarations. Very restricted; not like C++’sauto.u8character constants and improved UTF-8 support.Mandated two’s-complement for signed integers. (One’s-complement and sign-magnitude finally banned.)
VLAs remain optional — the standard didn’t rescind them but they’re not required.
Trigraphs finally removed. Nobody will miss
??=for#.
C23 Support in 2026 Compilers¶
Compiler |
C23 status |
Flag |
|---|---|---|
GCC 14 |
Substantial; use |
|
GCC 15 |
More complete. Ships in Ubuntu 25.10. |
|
Clang 18+ |
Reasonable coverage. |
|
MSVC |
Historically lags; check for latest status. |
|
Apple Clang |
Follows upstream Clang with a delay. |
|
Test support with a probe:
#include <stdio.h>
int main(void) {
#if __STDC_VERSION__ >= 202311L
puts("C23 or later");
#elif __STDC_VERSION__ >= 201710L
puts("C17");
#elif __STDC_VERSION__ >= 201112L
puts("C11");
#elif __STDC_VERSION__ >= 199901L
puts("C99");
#else
puts("C89 or unknown");
#endif
return 0;
}
What to Target on This Roadmap¶
Default:
-std=c17. Everywhere. It’s C11 with fixes, works on every compiler you’ll touch.Opt into C23 with a feature guard when you specifically want
nullptr,constexpr,<stdckdint.h>, or[[nodiscard]]:#if __STDC_VERSION__ >= 202311L [[nodiscard]] int compute(void); #else int compute(void); #endif
Never rely on
<threads.h>(uneven support), Annex K_sfunctions (universally disliked), or#embed(still landing in some compilers) without a fallback.
Portability Traps to Avoid¶
longis 32-bit on Windows, 64-bit on Linux/macOS. Use<stdint.h>for exact widths.charsignedness is platform-dependent. Signed on x86, often unsigned on ARM. Usesigned charorunsigned charwhen the bit-pattern matters.size_tprintf format is%zu. Not%lu. Not%d.ptrdiff_tprintf format is%td.Endianness — macOS and Linux on x86_64/arm64 are little-endian. Don’t assume forever, but for your Zoho/AWS/GCP targets, LE.
Compiler extensions.
__typeof__,__attribute__,__builtin_*are GCC/Clang; not portable to MSVC without shims. Isolate them behind macros.
What Most People Get Wrong About C Standards¶
They either write C89 out of habit and miss 25 years of ergonomic improvements, or they chase every C23 feature and their code stops compiling on their coworker’s slightly-older toolchain. The sweet spot is C17 as the target with selective C23 opt-in behind a version guard. This is roughly what the Linux kernel, PostgreSQL, and the Zephyr RTOS all do (with variations).
The second common mistake is trusting compiler defaults. GCC’s default has drifted over the years — GCC 5 defaulted to gnu11, GCC 11+ defaults to gnu17, and future GCC will drift to newer defaults. Always specify -std= explicitly. It costs one Makefile line and saves you from silent semantic shifts across compiler upgrades.
Recommended Reading Order¶
Beej’s Guide to C — free, ~200 pages, covers through C23. Read the whole thing this month.
Chapters 1-14 of King’s C Programming: A Modern Approach (C99 depth). ~500 pages, do exercises.
Skim Modern C by Jens Gustedt for the standards-lawyer view on any specific topic that surprises you.
Bookmark cppreference.com/w/c — not perfect but the best free reference for the standard library.
Do not read K&R (2nd ed) as your primary text. It’s a fine reference for someone who already knows C, but the r/C_Programming consensus in 2025 is that it was not written for absolute beginners and it’s missing 35+ years of ecosystem.
Return to README.md · Next: projects.md