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
boolfrom<stdbool.h>instead ofintfor boolean flags - Use
_Static_assertto verify struct sizes and platform assumptions at compile time - Use
_Genericfor type-safe macros that dispatch based on argument type - Enable these features with
-std=c99or-std=c11— not the default on some older compilers