A lot of C code still targets C89 out of habit, missing years of improvements. C99 and C11 added features that make C code safer, cleaner, and more expressive without adding complexity. Most are supported by GCC 4.8+ and Clang, and many are backported to even older compilers. Here is what is worth using today.

C99: Declarations Anywhere

#include <stdio.h>

int main() {
    /* C89: all declarations at the top of the block */
    int i, result, temp;
    i = 0; result = 0;

    /* C99: declare variables where you first need them */
    for (int i = 0; i < 10; i++) {  /* i scoped to the loop */
        int doubled = i * 2;         /* declared where it makes sense */
        printf("%dn", doubled);
    }
    /* i and doubled are out of scope here */

    return 0;
}

Declaring variables at first use reduces the distance between declaration and use, makes scope obvious, and eliminates the need to initialize everything to zero at the top of a block “just in case.”

C99: Designated Initializers

#include <stdio.h>

typedef struct {
    int    id;
    float  score;
    char   name[32];
    int    active;
} Student;

int main() {
    /* C89: positional, must match field order exactly */
    Student s1 = {1, 95.5f, "Alice", 1};

    /* C99: designated initializers — order doesn't matter, unset fields = 0 */
    Student s2 = {
        .name   = "Bob",
        .score  = 87.0f,
        .id     = 2,
        .active = 1,
    };

    /* Great for sparse initialization */
    Student empty = { .id = 3, .active = 0 };  /* score = 0.0, name = "" */

    printf("%s: %.1fn", s2.name, s2.score);
    return 0;
}

Designated initializers are invaluable for structs with many fields. They document which field is being set, survive field reordering, and zero-initialize unmentioned fields. Always use them when initializing complex structs.

C99: Compound Literals

#include <stdio.h>

typedef struct { int x; int y; } Point;

void print_point(Point p) {
    printf("(%d, %d)n", p.x, p.y);
}

int main() {
    /* Pass a struct literal directly — no named variable needed */
    print_point((Point){.x = 3, .y = 4});

    /* Array literal */
    int *arr = (int[]){1, 2, 3, 4, 5};

    return 0;
}

Compound literals create a temporary object at the point of use. They are useful for passing struct arguments inline and for creating short-lived arrays without naming them.

C99: stdint.h — Exact-Width Integer Types

#include <stdint.h>
#include <inttypes.h>
#include <stdio.h>

int main() {
    uint8_t  byte  = 255;
    uint16_t word  = 65535;
    uint32_t dword = 0xDEADBEEF;
    uint64_t qword = UINT64_MAX;

    /* Use PRIu32, PRId64 etc. for portable printf format strings */
    printf("%" PRIu32 "n", dword);   /* 3735928559 */
    printf("%" PRIu64 "n", qword);   /* 18446744073709551615 */

    return 0;
}

Use exact-width types from <stdint.h> whenever the size matters. Use <inttypes.h> for the corresponding printf/scanf format macros (PRId64, SCNu32, etc.) to avoid format specifier mismatch warnings.

C99: _Bool and stdbool.h

#include <stdbool.h>  /* provides bool, true, false */
#include <stdio.h>

bool is_valid(int x) {
    return x > 0 && x < 100;
}

int main() {
    bool flag = true;
    printf("%dn", flag);            /* 1 */
    printf("%dn", is_valid(50));   /* 1 */
    printf("%dn", is_valid(150));  /* 0 */
    return 0;
}

_Bool is a true boolean type (holds 0 or 1). stdbool.h defines bool, true, and false as macros over _Bool, 1, and 0. Use this instead of int for boolean flags — it documents intent and guarantees the value is always 0 or 1.

C11: _Static_assert

#include <stdint.h>
#include <assert.h>

typedef struct {
    uint32_t header;
    uint8_t  data[28];
} Packet;

/* Verify struct size matches wire format at compile time */
_Static_assert(sizeof(Packet) == 32, "Packet size must be 32 bytes");
_Static_assert(sizeof(int) >= 4, "Need at least 32-bit int");

int main() { return 0; }

_Static_assert (or static_assert with <assert.h>) evaluates a compile-time expression and fails compilation if false. Use it to verify struct sizes, type assumptions, and platform requirements — the error appears at compile time, not as a runtime crash.

C11: _Generic — Type-Based Dispatch

#include <stdio.h>

#define print_val(x) _Generic((x),  
    int:    printf("int: %dn",    (x)), 
    double: printf("double: %fn", (x)), 
    char*:  printf("string: %sn", (x)), 
    default: printf("unknownn")         
)

int main() {
    print_val(42);
    print_val(3.14);
    print_val("hello");
    return 0;
}

_Generic selects one of several expressions based on the type of its controlling expression, evaluated at compile time. This is how you implement type-safe macros in C without preprocessor hacks.

C11: Atomic Operations

#include <stdatomic.h>
#include <stdio.h>

_Atomic int counter = 0;

/* Increment is now atomic — safe for multi-threaded use without a mutex */
void increment(void) {
    atomic_fetch_add(&counter, 1);
}

int main() {
    increment();
    increment();
    printf("counter: %dn", atomic_load(&counter));  /* 2 */
    return 0;
}

Enable these features by specifying the standard to the compiler — add -std=c11 to your build. Compile and test with our c compiler online, which uses GCC and supports C99/C11. For the full set of build flags and when to use each standard, see our guide on GCC flags and C standards.

TL;DR

  • Declare variables at point of use — no need to hoist everything to the block start (C99)
  • Use designated initializers for struct init: .field = value — order-independent, self-documenting
  • Use exact-width types from <stdint.h>: uint32_t, int64_t — portable and explicit
  • Use bool from <stdbool.h> instead of int for boolean flags
  • Use _Static_assert to verify struct sizes and platform assumptions at compile time
  • Use _Generic for type-safe macros that dispatch based on argument type
  • Enable these features with -std=c99 or -std=c11 — not the default on some older compilers