|$ curl https://forge-ai.dev/api/markdown?path=docs/c/modern-c
$cat docs/c-—-modern-c-(c11/c17/c23).md
updated Recently·50 min read·published

C — Modern C (C11/C17/C23)

CC11C17C23ModernAdvancedAdvanced🎯Free Tools
C99 Features

C99 (ISO/IEC 9899:1999) was a major update to C that added inline functions, designated initializers, compound literals, variable-length arrays, and the _Bool type. Many C99 features are now ubiquitous.

c99_features.c
C
1#include <stdio.h>
2#include <stdbool.h>
3
4/* C99: inline functions */
5inline int square(int x) {
6 return x * x;
7}
8
9/* C99: designated initializers */
10struct Config {
11 int width;
12 int height;
13 bool fullscreen;
14 const char *title;
15};
16
17/* C99: compound literals */
18void print_coords(void) {
19 /* Create temporary array on the stack */
20 int *coords = (int[]){10, 20, 30};
21 for (int i = 0; i < 3; i++) {
22 printf("coord[%d] = %d\n", i, coords[i]);
23 }
24}
25
26int main(void) {
27 /* C99: mixed declarations and code */
28 printf("square(5) = %d\n", square(5));
29 int x = 10; /* Declaration after code — C99 allows this */
30
31 /* C99: designated initializers — order doesn't matter */
32 struct Config cfg = {
33 .title = "My App",
34 .width = 1920,
35 .height = 1080,
36 .fullscreen = true
37 };
38 printf("%s: %dx%d %s\n",
39 cfg.title, cfg.width, cfg.height,
40 cfg.fullscreen ? "fullscreen" : "windowed");
41
42 /* C99: compound literal for struct */
43 struct Config *temp = &(struct Config){
44 .width = 800, .height = 600, .fullscreen = false, .title = "Temp"
45 };
46 printf("temp: %dx%d\n", temp->width, temp->height);
47
48 /* C99: variable-length arrays */
49 int n = 5;
50 int vla[n];
51 for (int i = 0; i < n; i++) {
52 vla[i] = i * 10;
53 }
54
55 /* C99: __VA_ARGS__ in variadic macros */
56 #define LOG(fmt, ...) printf("LOG: " fmt "\n", __VA_ARGS__)
57 LOG("value: %d, name: %s", 42, "test");
58
59 /* C99: __func__ predefined identifier */
60 printf("Current function: %s\n", __func__);
61
62 return 0;
63}
📝

note

Variable-length arrays (VLAs) were added in C99 but are optional in C11. Many compilers support them as an extension. Avoid VLAs in production code — use malloc for dynamic arrays.
C99 Preprocessor Enhancements

C99 added __VA_ARGS__ for variadic macros, the __func__ predefined identifier, and single-line // comments.

c99_preprocessor.c
C
1#include <stdio.h>
2
3/* Variadic macro with __VA_ARGS__ */
4#define debug_print(fmt, ...) \
5 fprintf(stderr, "[%s:%d] " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__)
6
7/* __func__ — function name as string literal */
8void example_function(void) {
9 printf("Entering %s\n", __func__);
10}
11
12/* Token pasting with ## */
13#define MAKE_PAIR(type, name) \
14 type name##_first; \
15 type name##_second
16
17/* Stringification */
18#define STRINGIFY(x) #x
19#define TOSTRING(x) STRINGIFY(x)
20
21int main(void) {
22 debug_print("Starting program");
23 debug_print("Value: %d", 42);
24
25 example_function();
26
27 /* Token pasting */
28 MAKE_PAIR(int, point);
29 point_first = 10;
30 point_second = 20;
31 printf("point: (%d, %d)\n", point_first, point_second);
32
33 /* Stringification */
34 printf("Compiled: %s %s\n", __DATE__, __TIME__);
35 printf("Line: %d\n", __LINE__);
36
37 return 0;
38}
C11: Alignment Control

C11 added _Alignas for specifying alignment and _Alignof for querying it. The <stdalign.h> header provides nicer macros.

c11_alignment.c
C
1#include <stdio.h>
2#include <stdalign.h>
3
4/* Cache-line aligned structure */
5struct alignas(64) CacheLine {
6 volatile int data[16];
7};
8
9/* SIMD-aligned array */
10alignas(32) float simd_data[8] = {0};
11
12/* Aligned struct with specific alignment */
13struct alignas(16) AlignedData {
14 double x, y, z, w;
15};
16
17int main(void) {
18 printf("alignof(char): %zu\n", alignof(char));
19 printf("alignof(int): %zu\n", alignof(int));
20 printf("alignof(double): %zu\n", alignof(double));
21 printf("alignof(ptr): %zu\n", alignof(void *));
22 printf("alignof(CacheLine): %zu\n", alignof(struct CacheLine));
23 printf("sizeof(CacheLine): %zu\n", sizeof(struct CacheLine));
24 printf("alignof(AlignedData): %zu\n", alignof(struct AlignedData));
25
26 /* Dynamic aligned allocation (C11) */
27 void *p = NULL;
28 size_t alignment = 64;
29 size_t size = 256;
30 int err = aligned_alloc(alignment, size, &p);
31 if (err == 0 && p) {
32 printf("Allocated %zu bytes at %p (aligned to %zu)\n",
33 size, p, alignment);
34 free(p);
35 }
36
37 return 0;
38}
C11: Type-Generic Selection (_Generic)

_Generic provides compile-time type dispatch. It evaluates to one of several expressions based on the type of a controlling expression — like a type-safe switch statement.

c11_generic.c
C
1#include <stdio.h>
2#include <math.h>
3#include <string.h>
4
5/* Type-safe print macro */
6#define print(x) _Generic((x), \
7 int: printf("%d", x), \
8 long: printf("%ld", x), \
9 long long: printf("%lld", x), \
10 unsigned: printf("%u", x), \
11 double: printf("%f", x), \
12 float: printf("%f", (double)x), \
13 char *: printf("%s", x), \
14 const char *: printf("%s", x), \
15 default: printf("(unknown)") \
16)
17
18/* Type-safe absolute value */
19#define ABS(x) _Generic((x), \
20 int: abs(x), \
21 long: labs(x), \
22 long long: llabs(x), \
23 double: fabs(x), \
24 float: fabsf(x) \
25)
26
27/* Type-safe comparison */
28#define MAX(a, b) _Generic((a), \
29 int: ((a) > (b) ? (a) : (b)), \
30 double: fmax((double)(a), (double)(b)), \
31 float: fmaxf((float)(a), (float)(b)), \
32 default: ((a) > (b) ? (a) : (b)) \
33)
34
35int main(void) {
36 print(42);
37 printf("\n");
38 print(3.14);
39 printf("\n");
40 print("hello");
41 printf("\n");
42
43 printf("ABS(-10) = %d\n", ABS(-10));
44 printf("ABS(-3.14) = %f\n", ABS(-3.14));
45
46 printf("MAX(3, 7) = %d\n", MAX(3, 7));
47 printf("MAX(3.14, 2.71) = %f\n", MAX(3.14, 2.71));
48
49 return 0;
50}
C11: _Static_assert and _Noreturn

_Static_assert performs compile-time assertions with a descriptive error message. _Noreturn tells the compiler a function never returns (like exit()).

c11_static_assert.c
C
1#include <stdio.h>
2#include <stdlib.h>
3#include <stdnoreturn.h>
4#include <stdint.h>
5
6/* Compile-time assertions */
7_Static_assert(sizeof(int) >= 4,
8 "int must be at least 32 bits");
9_Static_assert(sizeof(void *) == 8 || sizeof(void *) == 4,
10 "pointer must be 32 or 64 bits");
11
12/* _Noreturn: function never returns */
13_Noreturn void fatal_error(const char *msg) {
14 fprintf(stderr, "FATAL: %s\n", msg);
15 exit(EXIT_FAILURE);
16 /* No return statement needed */
17}
18
19/* _Noreturn with _Generic */
20_Noreturn void abort_with_type(int code) {
21 fprintf(stderr, "Aborting with code %d\n", code);
22 exit(code);
23}
24
25/* Static assert in struct */
26struct Packet {
27 uint32_t header;
28 uint8_t payload[64];
29 uint32_t checksum;
30};
31_Static_assert(sizeof(struct Packet) == 72,
32 "Packet must be 72 bytes for wire format");
33
34int main(void) {
35 printf("Packet size: %zu\n", sizeof(struct Packet));
36
37 /* Use _Noreturn function */
38 if (sizeof(int) < 8) {
39 fatal_error("int too small");
40 }
41
42 printf("All checks passed\n");
43 return 0;
44}
C11: Anonymous Structs and Unions

C11 allows unnamed (anonymous) struct and union members. This enables overlapping access to the same memory through different field names — useful for register access and protocol parsing.

c11_anonymous.c
C
1#include <stdio.h>
2#include <stdint.h>
3
4/* Hardware register layout with overlapping fields */
5struct StatusReg {
6 uint32_t ready : 1;
7 uint32_t error : 1;
8 uint32_t mode : 3;
9 uint32_t reserved: 27;
10};
11
12/* Anonymous union for register access */
13struct Device {
14 uint32_t raw;
15 struct {
16 uint32_t tx_enable : 1;
17 uint32_t rx_enable : 1;
18 uint32_t speed : 3;
19 uint32_t duplex : 1;
20 uint32_t reserved : 26;
21 }; /* Anonymous struct member */
22};
23
24/* Network packet with anonymous union */
25struct PacketHeader {
26 uint16_t version;
27 uint16_t length;
28 union {
29 uint32_t raw_flags;
30 struct {
31 uint32_t syn : 1;
32 uint32_t ack : 1;
33 uint32_t fin : 1;
34 uint32_t rst : 1;
35 uint32_t padding : 28;
36 }; /* Anonymous struct */
37 };
38};
39
40int main(void) {
41 /* Access device through raw or structured fields */
42 struct Device dev = {.raw = 0};
43 dev.tx_enable = 1;
44 dev.speed = 3;
45 dev.duplex = 1;
46 printf("Device raw: 0x%08X\n", dev.raw);
47
48 /* Access packet flags through raw or structured */
49 struct PacketHeader pkt = {
50 .version = 1,
51 .length = 20,
52 .syn = 1
53 };
54 printf("Flags raw: 0x%08X\n", pkt.raw_flags);
55 printf("SYN=%d ACK=%d FIN=%d\n", pkt.syn, pkt.ack, pkt.fin);
56
57 return 0;
58}
C11: Atomics (stdatomic.h)

C11 provides standardized atomic operations via <stdatomic.h>. Atomics guarantee data-race-free concurrent access without locks, using memory ordering semantics.

c11_atomics.c
C
1#include <stdio.h>
2#include <stdatomic.h>
3
4static atomic_int g_counter = ATOMIC_VAR_INIT(0);
5static atomic_flag g_lock = ATOMIC_FLAG_INIT;
6
7/* Spinlock using atomic_flag */
8void spinlock_lock(void) {
9 while (atomic_flag_test_and_set(&g_lock)) {
10 /* Busy wait — spin until lock is acquired */
11 }
12}
13
14void spinlock_unlock(void) {
15 atomic_flag_clear(&g_lock);
16}
17
18/* Atomic operations demo */
19void atomic_demo(void) {
20 atomic_int val = ATOMIC_VAR_INIT(0);
21
22 /* Store with release semantics */
23 atomic_store_explicit(&val, 42, memory_order_release);
24
25 /* Load with acquire semantics */
26 int loaded = atomic_load_explicit(&val, memory_order_acquire);
27 printf("Loaded: %d\n", loaded);
28
29 /* Fetch-and-add */
30 int old = atomic_fetch_add(&val, 8);
31 printf("Old: %d, New: %d\n", old, atomic_load(&val));
32
33 /* Compare-and-swap (weak — may spuriously fail) */
34 int expected = 50;
35 int desired = 100;
36 bool ok = atomic_compare_exchange_weak(&val, &expected, desired);
37 printf("CAS: %s, expected=%d\n", ok ? "ok" : "fail", expected);
38
39 /* Exchange */
40 int prev = atomic_exchange(&val, 200);
41 printf("Exchanged: prev=%d, now=%d\n", prev, atomic_load(&val));
42}
43
44int main(void) {
45 atomic_demo();
46
47 /* Thread-safe counter increment */
48 for (int i = 0; i < 1000; i++) {
49 atomic_fetch_add(&g_counter, 1);
50 }
51 printf("Counter: %d\n", atomic_load(&g_counter));
52
53 return 0;
54}
C11: Threads (threads.h)

C11 standardized threading via <threads.h> — but it is optional and not widely implemented. Most production code uses pthreads (POSIX) or platform-specific APIs. Here is the standard API for reference.

c11_threads.c
C
1#include <stdio.h>
2#include <threads.h>
3
4/* Thread function */
5int thread_func(void *arg) {
6 int id = *(int *)arg;
7 printf("Thread %d started\n", id);
8
9 /* Simulate work */
10 for (int i = 0; i < 3; i++) {
11 printf("Thread %d: step %d\n", id, i);
12 }
13
14 printf("Thread %d finished\n", id);
15 return id * 10; /* Return value */
16}
17
18/* Mutex-protected shared data */
19mtx_t g_mutex;
20int g_shared = 0;
21
22int mutex_thread(void *arg) {
23 int id = *(int *)arg;
24 for (int i = 0; i < 5; i++) {
25 mtx_lock(&g_mutex);
26 g_shared++;
27 printf("Thread %d: shared = %d\n", id, g_shared);
28 mtx_unlock(&g_mutex);
29 }
30 return 0;
31}
32
33int main(void) {
34 /* Create and join threads */
35 thrd_t threads[3];
36 int ids[3] = {1, 2, 3};
37
38 for (int i = 0; i < 3; i++) {
39 thrd_create(&threads[i], thread_func, &ids[i]);
40 }
41
42 for (int i = 0; i < 3; i++) {
43 int res;
44 thrd_join(threads[i], &res);
45 printf("Thread %d returned %d\n", i + 1, res);
46 }
47
48 /* Mutex example */
49 mtx_init(&g_mutex, mtx_plain);
50 thrd_t t[2];
51 int tids[2] = {10, 20};
52 for (int i = 0; i < 2; i++) {
53 thrd_create(&t[i], mutex_thread, &tids[i]);
54 }
55 for (int i = 0; i < 2; i++) {
56 thrd_join(t[i], NULL);
57 }
58 mtx_destroy(&g_mutex);
59 printf("Final shared: %d\n", g_shared);
60
61 return 0;
62}
📝

note

On most systems, compile with -pthread flag. The C11 threads API is optional — check if your platform supports it with #ifdef __STDC_VERSION__ and __STDC_VERSION__ >= 201112L.
C17/C18 — Bug Fixes, Not New Features

C17 (ISO/IEC 9899:2018) is essentially C11 with defect reports resolved. It removed the optional Annex K (bounds-checking functions) from the mandatory spec. The key change was clarifying C11 ambiguities.

c17_changes.c
C
1#include <stdio.h>
2#include <stdalign.h>
3#include <stdatomic.h>
4#include <stdbool.h>
5#include <stdnoreturn.h>
6
7/*
8 * C17 differences from C11:
9 * - Defect reports resolved (DR 4xx, DR 5xx)
10 * - Annex K (gets_s, strcpy_s, etc.) removed from mandatory spec
11 * - Clarifications on _Generic, _Alignas, and _Atomic
12 * - No new keywords or features
13 *
14 * Compile with: gcc -std=c17 -Wall -Wextra
15 * Or use: gcc -std=c18 (same standard, different name)
16 */
17
18/* All C11 features still work in C17 */
19struct alignas(16) Data {
20 atomic_int counter;
21 _Atomic int atomic_counter;
22};
23
24_Noreturn void shutdown(void) {
25 printf("Shutting down...\n");
26 _Exit(0);
27}
28
29_Static_assert(sizeof(int) >= 4, "int too small");
30
31int main(void) {
32 printf("C version: %ld\n", __STDC_VERSION__);
33
34 /* C17/201710L, C11/201112L, C99/199901L */
35 #if __STDC_VERSION__ >= 201710L
36 printf("Compiled as C17 or later\n");
37 #elif __STDC_VERSION__ >= 201112L
38 printf("Compiled as C11\n");
39 #elif __STDC_VERSION__ >= 199901L
40 printf("Compiled as C99\n");
41 #else
42 printf("Compiled as C89/C90\n");
43 #endif
44
45 return 0;
46}

info

If your code compiles with C11, it will compile with C17. Use -std=c17 for the latest stable standard until C23 support matures.
C23: nullptr and nullptr_t

C23 adds nullptr and nullptr_t — a type-safe null pointer constant that replaces NULL. Unlike NULL, nullptr cannot be confused with integer zero.

c23_nullptr.c
C
1#include <stdio.h>
2#include <stddef.h>
3
4void func_int(int x) {
5 printf("func_int: %d\n", x);
6}
7
8void func_ptr(int *p) {
9 if (p == nullptr) { /* C23: type-safe null check */
10 printf("null pointer\n");
11 } else {
12 printf("value: %d\n", *p);
13 }
14}
15
16/* C23: nullptr_t type */
17void generic_null(nullptr_t n) {
18 printf("nullptr_t value: %d\n", n == nullptr);
19}
20
21int main(void) {
22 func_ptr(nullptr); /* C23: clear intent */
23 generic_null(nullptr);
24
25 /* C23: nullptr works in comparisons */
26 int *p = nullptr;
27 if (p == nullptr) {
28 printf("p is null\n");
29 }
30
31 /* C23: nullptr_t converts to any pointer type */
32 char *cp = nullptr;
33 double *dp = nullptr;
34 printf("cp == nullptr: %d\n", cp == nullptr);
35 printf("dp == nullptr: %d\n", dp == nullptr);
36
37 return 0;
38}
C23: typeof and constexpr

C23 adds typeof and typeof_unqual for querying types at compile time, plus constexpr for true compile-time constants.

c23_typeof.c
C
1#include <stdio.h>
2
3/* C23: typeof for variable declarations */
4#define SWAP(a, b) do { \
5 typeof(a) temp = a; \
6 a = b; \
7 b = temp; \
8} while(0)
9
10/* C23: constexpr for compile-time constants */
11constexpr int ARRAY_SIZE = 10;
12constexpr double PI = 3.14159265358979;
13
14/* C23: typeof in macros */
15#define PRINT_TYPE(x) \
16 do { \
17 typeof(x) val = x; \
18 printf("value: %g, size: %zu bytes\n", \
19 (double)val, sizeof(val)); \
20 } while(0)
21
22int main(void) {
23 int a = 10, b = 20;
24 SWAP(a, b);
25 printf("a=%d, b=%d\n", a, b);
26
27 double x = 3.14, y = 2.71;
28 SWAP(x, y);
29 printf("x=%f, y=%f\n", x, y);
30
31 /* C23: typeof_unqual strips qualifiers */
32 const int ci = 42;
33 typeof_unqual(ci) mutable = ci; /* mutable is just int */
34 mutable = 100;
35 printf("mutable: %d\n", mutable);
36
37 /* Compile-time sized array */
38 int arr[ARRAY_SIZE];
39 for (int i = 0; i < ARRAY_SIZE; i++) {
40 arr[i] = i * i;
41 }
42
43 PRINT_TYPE(42);
44 PRINT_TYPE(3.14);
45 PRINT_TYPE(100LL);
46
47 return 0;
48}
C23: Attributes and Keywords

C23 adds standard attributes from C++11: [[nodiscard]], [[maybe_unused]], [[deprecated]], and new keywords like unreachable(), auto (type deduction), and improved enum support.

c23_attributes.c
C
1#include <stdio.h>
2#include <stddef.h>
3
4/* C23: [[nodiscard]] — compiler warns if return value ignored */
5[[nodiscard]] int critical_operation(void) {
6 return 42;
7}
8
9/* C23: [[maybe_unused]] — suppress unused warnings */
10[[maybe_unused]] void helper(void) {
11 int x = 5;
12 (void)x;
13}
14
15/* C23: [[deprecated]] — compiler warns on use */
16[[deprecated("Use new_func instead")]]
17void old_func(void) {
18 printf("old_func\n");
19}
20
21void new_func(void) {
22 printf("new_func\n");
23}
24
25/* C23: auto for type deduction */
26auto get_value(void) {
27 return 42; /* auto deduces int */
28}
29
30/* C23: nullptr */
31[[nodiscard]] int *safe_get(void) {
32 return nullptr;
33}
34
35/* C23: unreachable() — mark code as dead */
36void process(int flag) {
37 if (flag == 1) {
38 printf("case 1\n");
39 } else if (flag == 2) {
40 printf("case 2\n");
41 } else {
42 unreachable(); /* Compiler knows this is dead code */
43 }
44}
45
46int main(void) {
47 /* [[nodiscard]] */
48 int result = critical_operation();
49 printf("Result: %d\n", result);
50
51 /* [[deprecated]] */
52 /* old_func(); — compiler warning */
53 new_func();
54
55 /* auto deduction */
56 auto val = get_value();
57 printf("val: %d\n", val);
58
59 /* unreachable */
60 process(1);
61
62 return 0;
63}
C23: #embed and Preprocessor

C23 adds #embed to include binary data directly in source code, #warning for compile-time warnings, #assert for preprocessor assertions, and #elifdef/#elifndef for cleaner conditional compilation.

c23_preprocessor.c
C
1#include <stdio.h>
2
3/* C23: #embed for binary data */
4/*
5 * Suppose you have a file "icon.bin":
6 * #embed "icon.bin"
7 * expands to comma-separated integer values.
8 *
9 * Usage:
10 * const unsigned char icon[] = {
11 * #embed "icon.bin"
12 * };
13 */
14
15/* C23: #warning */
16#if __STDC_VERSION__ < 202311L
17 #warning "Compile with -std=c23 for full C23 support"
18#endif
19
20/* C23: #assert */
21#define MAX_BUFFER 1024
22#if MAX_BUFFER < 256
23 #error "MAX_BUFFER must be at least 256"
24#endif
25
26/* C23: #elifdef / #elifndef */
27#define FEATURE_A
28/* #define FEATURE_B */
29
30#if defined(FEATURE_A)
31 printf("Feature A enabled\n");
32#elifdef FEATURE_B
33 printf("Feature B enabled\n");
34#else
35 printf("No features enabled\n");
36#endif
37
38/* C23: digit separators */
39int million = 1'000'000;
40long hex_val = 0xFF'FF'FF'FF;
41int binary = 0b1010'0101'1111'0000;
42
43int main(void) {
44 printf("million: %d\n", million);
45 printf("hex: 0x%lX\n", hex_val);
46 printf("binary: %d\n", binary);
47
48 return 0;
49}
C23: _BitInt — Arbitrary-Width Integers

C23 introduces _BitInt(N) for integers of any width from 1 to the implementation maximum. This is useful for bit-precise protocols, hardware registers, and cryptographic operations.

c23_bitint.c
C
1#include <stdio.h>
2#include <stdint.h>
3
4/* C23: _BitInt for precise widths */
5_BitInt(12) twelve_bit = 4095; /* 12-bit integer */
6_BitInt(24) twenty_four = 0xFFFFFF; /* 24-bit integer */
7_BitInt(64) sixty_four = 9223372036854775807LL;
8
9/* Unsigned variant */
10unsigned _BitInt(8) u8 = 255;
11unsigned _BitInt(16) u16 = 65535;
12unsigned _BitInt(32) u32 = 4294967295U;
13
14/* Useful for bit-field-like structures without the overhead */
15struct NetworkPacket {
16 unsigned _BitInt(4) version;
17 unsigned _BitInt(12) length;
18 unsigned _BitInt(16) flags;
19 unsigned _BitInt(32) sequence;
20};
21
22int main(void) {
23 printf("_BitInt(12) max: %d\n", twelve_bit);
24 printf("_BitInt(24) max: %d\n", twenty_four);
25
26 struct NetworkPacket pkt = {
27 .version = 4,
28 .length = 1500,
29 .flags = 0x01,
30 .sequence = 12345
31 };
32 printf("Packet: v=%d len=%d flags=0x%X seq=%u\n",
33 (int)pkt.version, (int)pkt.length,
34 (unsigned)pkt.flags, (unsigned)pkt.sequence);
35
36 /* Arithmetic works normally */
37 _BitInt(8) a = 200, b = 100;
38 _BitInt(8) sum = a + b; /* Wraps at 256 */
39 printf("200 + 100 = %d (mod 256)\n", (int)sum);
40
41 return 0;
42}

warning

_BitInt support varies by compiler. GCC and Clang support it; MSVC support is evolving. Check __STDC_VERSION__ >= 202311L and compiler-specific macros.
C23: Improved Enums

C23 allows specifying the underlying type of enums (like C++ enum class), enables forward declarations, and makes enums work with typeof.

c23_enum.c
C
1#include <stdio.h>
2
3/* C23: enum with explicit underlying type */
4enum Color : unsigned char {
5 RED = 0,
6 GREEN = 1,
7 BLUE = 2
8};
9
10/* C23: forward declaration of enums */
11enum Status : int; /* Forward declaration */
12enum Status {
13 OK = 0,
14 ERROR = -1,
15 PENDING = 1
16};
17
18/* C23: typeof works with enum values */
19typeof(RED) current_color = GREEN;
20
21int main(void) {
22 enum Color c = BLUE;
23 printf("Color value: %d\n", c);
24 printf("Color size: %zu bytes\n", sizeof(enum Color));
25
26 enum Status s = OK;
27 printf("Status value: %d\n", s);
28
29 current_color = RED;
30 printf("current_color: %d\n", current_color);
31
32 /* Enum in switch */
33 switch (c) {
34 case RED: printf("Red\n"); break;
35 case GREEN: printf("Green\n"); break;
36 case BLUE: printf("Blue\n"); break;
37 }
38
39 return 0;
40}
Practical Modern C Patterns

Modern C features enable cleaner, safer patterns. Here are practical examples combining C11/C23 features for real-world code.

modern_patterns.c
C
1#include <stdio.h>
2#include <stdatomic.h>
3#include <stdbool.h>
4#include <threads.h>
5
6/* Pattern 1: Type-safe container using _Generic */
7#define container_of(ptr, type, member) \
8 ((type *)((char *)(ptr) - offsetof(type, member)))
9
10#define print_container(ptr, type) _Generic((ptr), \
11 type *: printf("Container value: %d\n", (ptr)->value) \
12)
13
14/* Pattern 2: Designated initializer for config */
15struct ServerConfig {
16 const char *host;
17 int port;
18 bool use_tls;
19 int max_connections;
20};
21
22struct ServerConfig default_config(void) {
23 return (struct ServerConfig){
24 .host = "localhost",
25 .port = 8080,
26 .use_tls = false,
27 .max_connections = 100
28 };
29}
30
31/* Pattern 3: Atomic flag for once-init */
32static atomic_flag g_initialized = ATOMIC_FLAG_INIT;
33
34void init_once(void) {
35 if (atomic_flag_test_and_set(&g_initialized)) {
36 return; /* Already initialized */
37 }
38 printf("Initializing...\n");
39}
40
41/* Pattern 4: Thread-safe ring buffer */
42#define RING_SIZE 256
43
44struct RingBuffer {
45 atomic_int head;
46 atomic_int tail;
47 int data[RING_SIZE];
48};
49
50bool ring_push(struct RingBuffer *rb, int val) {
51 int head = atomic_load(&rb->head);
52 int next = (head + 1) % RING_SIZE;
53 if (next == atomic_load(&rb->tail)) {
54 return false; /* Full */
55 }
56 rb->data[head] = val;
57 atomic_store(&rb->head, next);
58 return true;
59}
60
61bool ring_pop(struct RingBuffer *rb, int *val) {
62 int tail = atomic_load(&rb->tail);
63 if (tail == atomic_load(&rb->head)) {
64 return false; /* Empty */
65 }
66 *val = rb->data[tail];
67 atomic_store(&rb->tail, (tail + 1) % RING_SIZE);
68 return true;
69}
70
71int main(void) {
72 /* Pattern 1: Designated initializer */
73 struct ServerConfig cfg = default_config();
74 printf("Server: %s:%d\n", cfg.host, cfg.port);
75
76 /* Pattern 2: Compound literal override */
77 struct ServerConfig custom = {
78 .host = "0.0.0.0",
79 .port = 443,
80 .use_tls = true
81 };
82 printf("Custom: %s:%d TLS=%d\n",
83 custom.host, custom.port, custom.use_tls);
84
85 /* Pattern 3: Once-init */
86 init_once();
87 init_once(); /* No-op */
88
89 /* Pattern 4: Ring buffer */
90 struct RingBuffer rb = {0};
91 ring_push(&rb, 42);
92 ring_push(&rb, 99);
93 int val;
94 ring_pop(&rb, &val);
95 printf("Popped: %d\n", val);
96
97 return 0;
98}
Compiler Support and Compilation

Different compilers support different C standards. Always check __STDC_VERSION__ and compile with explicit standard flags.

CompilerC17 FlagC23 FlagNotes
GCC-std=c17-std=c23Full C23 support since GCC 13
Clang-std=c17-std=c23C23 since Clang 17
MSVC/std:c17/std:c23C23 in VS 2022 17.8+
TCC-std=c11N/ALimited C23 support
compiler_check.c
C
1#include <stdio.h>
2
3/* Detect C standard version */
4void print_c_version(void) {
5 #if __STDC_VERSION__ >= 202311L
6 printf("C23 (ISO/IEC 9899:2024)\n");
7 #elif __STDC_VERSION__ >= 201710L
8 printf("C17 (ISO/IEC 9899:2018)\n");
9 #elif __STDC_VERSION__ >= 201112L
10 printf("C11 (ISO/IEC 9899:2011)\n");
11 #elif __STDC_VERSION__ >= 199901L
12 printf("C99 (ISO/IEC 9899:1999)\n");
13 #else
14 printf("C89/C90\n");
15 #endif
16}
17
18/* Detect compiler extensions */
19void print_extensions(void) {
20 #ifdef __GNUC__
21 printf("GCC/Clang extensions available\n");
22 #endif
23 #ifdef _MSC_VER
24 printf("MSVC extensions available\n");
25 #endif
26 #ifdef __STDC_VERSION__
27 printf("__STDC_VERSION__ = %ld\n", __STDC_VERSION__);
28 #else
29 printf("__STDC_VERSION__ not defined\n");
30 #endif
31}
32
33int main(void) {
34 print_c_version();
35 print_extensions();
36 return 0;
37}

best practice

Always compile with -Wall -Wextra -Wpedantic to catch non-standard usage. Use -std=c17 as your baseline standard for production code.
Migration Guide: C89 to Modern C

Migrating from C89 to modern C improves safety, readability, and performance. Here is a practical migration checklist.

C89 PatternModern ReplacementStandard
typedef enum { YES=1, NO=0 } bool;bool from <stdbool.h>C99
/* block comments only */// single-line commentsC99
int arr[3] = {1, 2, 3};.field = value designatorsC99
malloc + memset initCompound literalsC99
volatile int flag;atomic_int flag;C11
pthread_create()thrd_create() (if available)C11
#define MIN(a,b) ...#define MIN(a,b) _Generic(...)C11
NULL for pointersnullptrC23
migration_example.c
C
1#include <stdio.h>
2#include <stdbool.h>
3#include <stdatomic.h>
4
5/* C89 style (BAD): */
6/* typedef struct { int x; int y; } Point; */
7/* void init_point(Point *p) { p->x = 0; p->y = 0; } */
8
9/* Modern C style (GOOD): */
10typedef struct {
11 int x;
12 int y;
13} Point;
14
15Point point_new(int x, int y) {
16 return (Point){.x = x, .y = y};
17}
18
19/* C89: volatile for flags (WRONG for threads) */
20/* volatile int counter = 0; */
21
22/* Modern: atomic for flags (CORRECT) */
23atomic_int counter = ATOMIC_VAR_INIT(0);
24
25int main(void) {
26 /* Modern: compound literals and designated initializers */
27 Point p1 = point_new(10, 20);
28 Point p2 = {.x = 30, .y = 40};
29
30 printf("p1: (%d, %d)\n", p1.x, p1.y);
31 printf("p2: (%d, %d)\n", p2.x, p2.y);
32
33 /* Modern: atomic operations */
34 atomic_fetch_add(&counter, 1);
35 printf("Counter: %d\n", atomic_load(&counter));
36
37 /* Modern: nullptr (C23) */
38 Point *pp = nullptr;
39 if (pp == nullptr) {
40 printf("pp is null\n");
41 }
42
43 return 0;
44}
$Blueprint — Engineering Documentation·Section ID: C-MODERN-C·Revision: 1.0