C reference

Compiler invocation, language-mode macros, the keyword set of every revision, operator precedence, the standard headers, conversion specifiers, the undefined behaviours that actually bite, and the hardening flags that catch them. The language is described in the C article; the C ecosystem map covers Linux, embedded systems, assembly, robotics, drones, assurance, and FFmpeg; this page is the lookup companion to both.

Toolchain and command line

GCC and Clang share their option surface almost exactly; MSVC does not. The rows below are the options that change the meaning of a build rather than its speed.

OptionWhat it doesExampleNote
cc / gcc / clangCompiler driver: preprocess, compile, assemble, linkcc -std=c23 -O2 -o app main.c util.cThe driver, not the compiler; it calls the others
-cCompile to an object file, do not linkcc -c -std=c17 util.cProduces util.o
-EPreprocess onlycc -E main.c | lessThe first tool to reach for on a macro bug
-SCompile to assemblycc -S -O2 -masm=intel hot.cReading the output settles most performance arguments
-I, -L, -lHeader search, library search, library to linkcc main.c -Iinclude -L/opt/lib -lz-l comes after the objects that need it
-D, -UDefine or undefine a macro on the command linecc -DNDEBUG -D_FORTIFY_SOURCE=3 main.cNDEBUG disables assert
-gEmit debug informationcc -g -Og main.c-Og is the optimisation level meant for debugging
-O0-O3, -Os, -OfastOptimisation levelcc -O2 main.c-Ofast relaxes IEEE conformance; treat it as a different language
-fltoLink-time optimisation across translation unitscc -O2 -flto *.cThe only way to inline across the unit boundary
-Wall -WextraThe warning sets worth enabling everywherecc -Wall -Wextra -Wpedantic -Werror main.cNeither includes all warnings, despite the name
-ffreestandingDo not assume a hosted library or that main is specialcc -ffreestanding -nostdlib boot.cKernel and firmware builds
-fno-strict-aliasingDisable type-based alias analysiscc -O2 -fno-strict-aliasing net.cHow the Linux kernel and much legacy code stay correct
cl.exeThe MSVC drivercl /std:c17 /W4 /O2 main.c/std:clatest for the C23 subset

Driver options in common use. GCC and Clang accept all of the Unix-form options listed; the MSVC equivalents differ in both spelling and default. Source: GCC and Clang command-line documentation and the MSVC compiler options reference.

# A build that catches what a build can catch, before the sanitizers run.
cc -std=c23 -Wall -Wextra -Wpedantic -Werror \
   -O2 -g -D_FORTIFY_SOURCE=3 -fstack-protector-strong \
   -fno-common -Wconversion -Wshadow \
   -o app main.c util.c

Back to top

Language modes and feature tests

RevisionCompiler option__STDC_VERSION__Note
C89 / C90-std=c89, -std=iso9899:1990not defined-std=gnu89 for the GNU dialect
C95-std=iso9899:199409199409LRarely selected explicitly
C99-std=c99199901L-std=gnu99 adds GNU extensions
C11-std=c11201112LMSVC: /std:c11
C17 / C18-std=c17201710LThe default in Clang and in GCC before 15; MSVC /std:c17
C23-std=c23 (-std=c2x in older releases)202311LThe default in GCC 15 and later as gnu23; MSVC subset under /std:clatest
C2y-std=c2y where supportedimplementation-defined until publishedWorking draft; contents may change

Selecting a language mode, and the macro value that identifies it. The gnu* variants of each option enable GNU extensions on top of the same standard. Source: GCC and Clang option documentation; ISO/IEC 9899:2024 §6.10.10.

MacroMeaningNote
__STDC__1 in a conforming implementationPresent since C89
__STDC_VERSION__The revision, as in the table aboveAbsent in C89; the standard mode test
__STDC_HOSTED__1 hosted, 0 freestandingThe portable way to ask whether the full library exists
__STDC_NO_ATOMICS__Defined if <stdatomic.h> is absentConditional feature
__STDC_NO_THREADS__Defined if <threads.h> is absentDefined by several mainstream implementations
__STDC_NO_VLA__Defined if variable-length arrays are absentVLAs became optional in C11
__STDC_NO_COMPLEX__Defined if complex arithmetic is absentOptional since C11
__STDC_IEC_60559_BFP__IEEE-754 binary floating point conformanceReplaces C99’s __STDC_IEC_559__
__has_include(...)Whether a header can be includedC23; available as an extension in GCC and Clang long before
__has_c_attribute(...)Whether a standard attribute is supportedC23

Predefined macros used to compile the same source against several revisions and implementations. Source: ISO/IEC 9899:2024 §6.10.10.

#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L
  typedef typeof(0) counter;             /* C23 */
#else
  #include <stdbool.h>                   /* C99/C11: bool is a macro */
  typedef int counter;
#endif

#if !defined(__STDC_HOSTED__) || !__STDC_HOSTED__
  #define LOG(...) ((void)0)             /* freestanding: no stdio */
#endif

Back to top

Keywords by revision

Each revision only adds. No keyword has ever been removed, which is why a 1990 source file still parses: the cost is paid in the underscore-capital spellings, chosen precisely because no conforming program could already have used them as identifiers.

RevisionCountKeywords addedNote
C89 / C9032auto, break, case, char, const, continue, default, do, double, else, enum, extern, float, for, goto, if, int, long, register, return, short, signed, sizeof, static, struct, switch, typedef, union, unsigned, void, volatile, whileThe original set; unchanged by C95
C99+5 = 37inline, restrict, _Bool, _Complex, _ImaginaryThe underscore-capital spellings avoid colliding with existing identifiers
C11+7 = 44_Alignas, _Alignof, _Atomic, _Generic, _Noreturn, _Static_assert, _Thread_localC17 adds none. <stdalign.h>, <stdbool.h> and <stdnoreturn.h> provide the friendly spellings
C23+15 = 59alignas, alignof, bool, constexpr, false, nullptr, static_assert, thread_local, true, typeof, typeof_unqual, _BitInt, _Decimal32, _Decimal64, _Decimal128The friendly spellings become keywords in their own right; the underscore forms are kept for compatibility

The keyword set, cumulative. C23’s 59 entries include the fourteen underscore-prefixed spellings retained from C99 and C11 plus _BitInt. These counts are the source of the chart in the article’s standard lineage. Source: ISO/IEC 9899:2024 §6.4.1 and the corresponding clause of each earlier revision.

Back to top

Types, limits, and fixed-width integers

C guarantees minimum ranges, not sizes. The exact-width types in <stdint.h> exist for code that needs a layout; the least- and fast- families exist for code that needs a range and should not fail to compile on a target without that exact width.

TypeSizeGuaranteed rangeNote
char1 byte by definitionat least 8 bitsSignedness is implementation-defined; a distinct type from both signed char and unsigned char
shortat least 2 bytes±32767Promoted to int in expressions
intat least 2 bytes; 4 in practice±32767The type integer constants and promotions land in
longat least 4 bytes±21474836478 bytes on LP64 (Unix), 4 on LLP64 (Windows) — the classic portability trap
long longat least 8 bytes±9223372036854775807C99
bool1 byte typicallytrue or false_Bool in C99–C17; converts any nonzero value to 1
float, double, long double4, 8, and 8–16 bytesIEEE-754 where __STDC_IEC_60559_BFP__ is definedlong double is 80-bit extended on x86 Unix, 64-bit on MSVC
_BitInt(N)exactly N bitsas declaredC23; the first standard type with a programmer-chosen width
size_t, ptrdiff_ttarget-dependentunsigned / signedThe types of sizeof and of pointer difference
intN_t, uintN_texactly N bitsexactOptional; absent if the target has no such type
int_leastN_t, int_fastN_tat least N bitsat leastAlways present; the portable choice
intmax_t, uintmax_twidest supportedwidestAlso intptr_t and uintptr_t, both optional

Types and their guarantees. Sizes described as ‘in practice’ are what mainstream 64-bit targets do, not requirements. Source: ISO/IEC 9899:2024 §6.2.5, §7.20 and <limits.h>.

/* Integer promotion and the usual arithmetic conversions, in three lines
   that surprise everyone once. */
unsigned char a = 200, b = 200;
int c = a + b;                 /* 400: both promote to int before adding    */

int i = -1;
unsigned u = 1;
if (i < u) { /* not taken */ } /* i converts to unsigned: 4294967295 < 1    */

char x = 'A';
printf("%zu\n", sizeof x);     /* 1 */
printf("%zu\n", sizeof +x);    /* 4: unary + promotes                      */

Two rules generate most integer bugs. Integer promotion converts anything narrower than int to int before arithmetic. The usual arithmetic conversions then bring both operands to a common type, and when the operands have the same rank but different signedness, the unsigned type wins. Signed overflow is undefined; unsigned arithmetic wraps by definition. C23’s <stdckdint.h> is the first standard facility for detecting the former without invoking it.

Back to top

Operators and precedence

Sixteen levels, highest binding first. Two of them are historical accidents that still cause bugs and are flagged in the notes: shift binds looser than addition, and the bitwise operators bind looser than the equality operators.

LevelOperatorsAssociativityGroup and note
1a[i]   f(...)   .   ->   a++   a--   (type){...}Left to rightPostfix
2++a   --a   +a   -a   !   ~   *p   &a   sizeof   alignofRight to leftUnary
3(type)xRight to leftCast
4*   /   %Left to rightMultiplicative
5+   -Left to rightAdditive
6<<   >>Left to rightShift — lower than additive, which is why a << b + c shifts by b + c
7<   <=   >   >=Left to rightRelational
8==   !=Left to rightEquality
9&Left to rightBitwise AND — lower than equality, so x & 1 == 0 parses as x & (1 == 0)
10^Left to rightBitwise XOR
11|Left to rightBitwise OR
12&&Left to rightLogical AND; sequences and short-circuits
13||Left to rightLogical OR; sequences and short-circuits
14?:Right to leftConditional; evaluates exactly one branch
15=   +=   -=   *=   /=   %=   <<=   >>=   &=   ^=   |=Right to leftAssignment
16,Left to rightComma; sequences its operands and yields the right one

Operator precedence and associativity. The levels are a conventional rendering of the grammar in §6.5, which expresses precedence through nested production rules rather than a table. Precedence governs parsing only: except for &&, ||, ?: and ,, it says nothing about evaluation order. Source: ISO/IEC 9899:2024 §6.5.

Back to top

Reading a declaration

C declarations mirror use: the declaration of p is written as the expression that yields the base type. Read from the identifier, then right, then left, honouring parentheses.

Function declarators are where the rule earns its keep, because a function pointer, a function returning a pointer, and an array of function pointers differ only in where the parentheses sit. What each one is for — dispatch tables, callbacks, and the reason a function is not itself a value — is in the article, under function pointers and indirect calls.

DeclarationReads asWhy
int *p[10]array of 10 pointers to int[] binds tighter than *
int (*p)[10]pointer to an array of 10 intParentheses force the pointer first
int *f(void)function returning pointer to int() binds tighter than *
int (*f)(void)pointer to function returning intThe form every callback table uses
int (*fa[5])(void)array of 5 pointers to function returning intRead outward from fa
const char *ppointer to const char — the characters are read-onlyp may be reassigned
char * const pconst pointer to char — the pointer is read-only*p may be written
const char * const pconst pointer to const charBoth fixed
char *restrict ppointer promising exclusive access to what it points atA promise to the optimiser, checked by nobody

The declarations that are misread most often. typedef exists largely to stop the last three rows from nesting. Source: ISO/IEC 9899:2024 §6.7.6.

/* The same type, twice: once inline, once via typedef. */
void (*signal(int sig, void (*handler)(int)))(int);

typedef void handler_t(int);
handler_t *signal(int sig, handler_t *handler);   /* identical, readable */

Back to top

Preprocessor directives and predefined macros

The preprocessor is a separate language operating on token sequences, executed in translation phase 4 before any C construct is parsed. It has no types, no scope, and no knowledge of the program — which is both why it is portable and why every C project eventually contains a macro nobody wants to touch.

DirectiveWhat it doesNote
#includeTextual inclusion of a header or source file<…> searches the system paths, "…" the current directory first
#define, #undefObject-like and function-like macros# stringises a parameter, ## pastes tokens
#if, #elif, #else, #endifConditional compilationOnly integer constant expressions and defined are evaluable
#ifdef, #ifndefShorthand for #if defined(X)C23 adds #elifdef and #elifndef
#error, #warningStop, or complain, at preprocessing time#warning standardised in C23 after decades as an extension
#pragmaImplementation-defined directive_Pragma("…") is the operator form, usable inside a macro
#embedInclude a binary resource as a list of integer constantsC23; replaces build-time xxd -i generators
#lineReset the reported line number and file nameUsed by code generators

Preprocessing directives. Source: ISO/IEC 9899:2024 §6.10.

MacroExpands toNote
__FILE__, __LINE__Current file name and line numberThe basis of assert
__DATE__, __TIME__Translation date and timeDefeats reproducible builds; avoid
__func__The enclosing function’s nameC99; an identifier, not a macro, so it cannot be pasted
__VA_ARGS__, __VA_OPT__Variadic macro arguments; C23 adds the conditional form__VA_OPT__(,) solves the trailing-comma problem
__COUNTER__A distinct integer per expansionGCC/Clang/MSVC extension, not standard

Predefined macros other than the feature-test macros listed under Language modes. Source: ISO/IEC 9899:2024 §6.10.10.

/* The two idioms worth memorising. */
#define STR_(x) #x
#define STR(x)  STR_(x)          /* expands x before stringising it */

#define LOG(fmt, ...) \
    fprintf(stderr, "%s:%d: " fmt "\n", __FILE__, __LINE__ __VA_OPT__(,) __VA_ARGS__)

Back to top

Standard headers by revision

RevisionTotalHeaders addedNote
C89 / C9015<assert.h>, <ctype.h>, <errno.h>, <float.h>, <limits.h>, <locale.h>, <math.h>, <setjmp.h>, <signal.h>, <stdarg.h>, <stddef.h>, <stdio.h>, <stdlib.h>, <string.h>, <time.h>The original library
C95+3 = 18<iso646.h>, <wchar.h>, <wctype.h>Wide characters and digraphs
C99+6 = 24<complex.h>, <fenv.h>, <inttypes.h>, <stdbool.h>, <stdint.h>, <tgmath.h><complex.h> and <tgmath.h> became optional in C11
C11+5 = 29<stdalign.h>, <stdatomic.h>, <stdnoreturn.h>, <threads.h>, <uchar.h><threads.h> and <stdatomic.h> are conditional features
C23+2 = 31<stdbit.h>, <stdckdint.h><stdalign.h>, <stdbool.h> and <stdnoreturn.h> become obsolescent, since their contents are keywords

The standard headers, cumulative. These counts are the second series in the chart on the article’s standard lineage. Source: clause 7 of each revision.

A freestanding implementation must provide only the headers that declare no functions, which in C23 are <float.h>, <iso646.h>, <limits.h>, <stdalign.h>, <stdarg.h>, <stdbit.h>, <stdbool.h>, <stddef.h>, <stdint.h>, <stdnoreturn.h> and <stddef.h>’s type definitions. Everything else — including <stdio.h> and <stdlib.h> — is optional, which is what makes kernel and firmware C conforming rather than a dialect.

Back to top

Standard library map

The C standard library is small by modern standards: no containers, no networking, no filesystem traversal, no regular expressions, no string type. Those live in POSIX, in the platform SDK, or in a third-party library, which is why a portable C project’s dependency list starts where this table stops.

Header groupCoversRepresentative namesNote
<stdio.h>Streams and formatted I/Ofopen, fread, printf, snprintf, fgetsgets was removed in C11; snprintf is the only safe formatter
<stdlib.h>Allocation, conversion, process control, sortingmalloc, free, strtol, qsort, abortatoi cannot report failure; use strtol
<string.h>Byte and string handlingmemcpy, memmove, strlen, strncpy, memcmpstrncpy does not guarantee termination; memcpy requires non-overlap
<stdint.h>, <inttypes.h>Fixed-width integers and their format macrosuint32_t, INT64_MAX, PRIu64The portable way to print a fixed-width type
<math.h>, <fenv.h>, <tgmath.h>Floating point, rounding modes, type-generic wrappersfma, nextafter, fesetroundLink with -lm on Unix
<stdatomic.h>, <threads.h>Atomics, threads, mutexes, condition variablesatomic_load_explicit, thrd_create, mtx_lockBoth conditional; check the __STDC_NO_* macros
<stdbit.h>Bit counting and rounding (C23)stdc_leading_zeros, stdc_popcount, stdc_bit_ceilStandardises what was previously a compiler builtin
<stdckdint.h>Checked integer arithmetic (C23)ckd_add, ckd_sub, ckd_mulReturns whether the operation overflowed rather than invoking undefined behaviour
<setjmp.h>, <signal.h>Non-local jumps and signal handlingsetjmp, longjmp, signalWhat a signal handler may portably do is a very short list
<time.h>, <locale.h>Calendar time, clocks, locale selectionclock_gettime (POSIX), timespec_get, setlocaletimespec_get is the C11 portable monotonic-ish clock
<wchar.h>, <uchar.h>Wide and Unicode charactersmbrtowc, c16rtomb, char8_twchar_t is 16-bit on Windows, 32-bit on Unix — not a portable encoding

The standard library by area, with the traps that most often appear in review. Source: ISO/IEC 9899:2024 clause 7.

Back to top

Conversion specifiers

Format strings are unchecked at run time: the variadic call passes what the caller wrote, and a mismatch between the specifier and the argument is undefined behaviour. Compilers do check literal format strings — -Wformat is enabled by -Wall — which is the single best reason never to build a format string dynamically.

SpecifierArgument typeMeaningNote
%d, %iintSigned decimal%i differs from %d only in scanf, where it accepts 0x and 0 prefixes
%u, %o, %x, %Xunsigned intDecimal, octal, hexadecimal%#x prefixes 0x
%b, %Bunsigned intBinary (C23)New in C23
%f, %e, %g, %adoubleFixed, scientific, shorter of the two, hexadecimal float%a round-trips exactly; %g does not
%c, %sint, char *Character, string%.*s takes a precision argument and does not require termination
%pvoid *Pointer, implementation-defined formCast the argument; passing any other pointer type is undefined
%nint *Stores the count written so farA format-string attack primitive; disabled by hardened libcs
%%A literal percent sign 
hh, hchar, shortLength modifierArguments still arrive promoted to int
l, lllong, long longLength modifier%ld is wrong for int64_t on Windows
z, t, jsize_t, ptrdiff_t, intmax_tLength modifier%zu is the correct specifier for a sizeof result
Llong doubleLength modifier 
PRId64, PRIu32, …Fixed-width typesMacros from <inttypes.h>The only portable way to print int64_t: printf("%" PRId64, x)

Conversion specifiers, then length modifiers. Both tables apply to the whole printf and scanf family; scanf additionally requires a pointer argument and a width for %s. Source: ISO/IEC 9899:2024 §7.23.6.

Back to top

Undefined behaviour: the common cases

Annex J.2 of the standard lists undefined behaviour in the low hundreds and is explicitly not exhaustive. The rows below are the ones that appear in real defect reports, each paired with the tool that finds it. The classification and the reasoning behind it are in the article’s treatment of behaviour classes.

CaseWhat triggers itWhat finds it
Out-of-bounds accessa[n] where n >= the element countAddressSanitizer; -fsanitize=bounds; -fbounds-safety where available
Use after free, double freeTouching memory after free, or freeing twiceAddressSanitizer; hardened allocators; -fsanitize=address in CI
Signed integer overflowINT_MAX + 1, -INT_MIN, 1 << 31 on int-fsanitize=signed-integer-overflow; <stdckdint.h>; -fwrapv to define it
Uninitialised readReading an automatic object before storing to itMemorySanitizer; -ftrivial-auto-var-init=zero; -Wmaybe-uninitialized
Null dereferenceDereferencing a pointer that may be null, including after a checked allocation-fsanitize=null; nullability annotations in Clang
Strict-aliasing violationReading an object through an incompatible lvalue type-fstrict-aliasing -Wstrict-aliasing; use memcpy or a union
Misaligned accessCasting a char * to a wider type at an odd offset-fsanitize=alignment; memcpy into an aligned object
Data raceTwo threads, one write, no synchronisationThreadSanitizer; _Atomic; a mutex
Unsequenced modificationi = i++, a[i] = i++-Wsequence-point (GCC), -Wunsequenced (Clang)
Library preconditionsmemcpy with overlapping ranges, strcpy into a short buffer, free of a non-allocated pointer_FORTIFY_SOURCE=3; memmove; bounded functions
Lifetime escapeReturning the address of an automatic object, or of a compound literal in a nested block-Wreturn-local-addr; AddressSanitizer’s stack-use-after-return mode

The undefined behaviours worth building a pipeline around. None of these is diagnosed reliably by the compiler alone. Source: ISO/IEC 9899:2024 Annex J.2, and the GCC and LLVM sanitizer documentation.

Back to top

Sanitizers and hardening flags

Worked transcripts of ThreadSanitizer catching a data race and a lock-order inversion — in programs that print the right answer and never hang — are on the synchronisation page.

Two distinct sets, and confusing them wastes both. Sanitizers are test-time instrumentation that finds undefined behaviour when the test happens to reach it. Hardening flags are release-time mitigations that turn an exploitable defect into a crash. A project that is serious about C uses both, and neither is a substitute for the other.

FlagWhenWhat it catches or mitigatesCost and caveat
-fsanitize=addressTest buildsOut-of-bounds, use-after-free, leaks~2× slower; incompatible with the other sanitizers except UBSan
-fsanitize=undefinedTest buildsOverflow, misalignment, invalid shifts, nullCheap enough that some projects ship it with -fsanitize-trap
-fsanitize=threadTest buildsData racesLarge memory overhead; run separately
-fsanitize=memoryTest buildsUninitialised readsClang only; needs an instrumented libc
-D_FORTIFY_SOURCE=3Release buildsBounded versions of memcpy, sprintf and friendsRequires optimisation to be enabled; level 3 needs GCC 12 or Clang 15
-fstack-protector-strongRelease buildsStack canaries on functions with local buffersThe strong variant is the accepted default; -all costs more
-ftrivial-auto-var-init=zeroRelease buildsZeroes uninitialised localsTurns an information leak into a deterministic bug
-fstack-clash-protectionRelease buildsLarge stack allocations skipping the guard page 
-fcf-protection=full, -mbranch-protection=standardRelease buildsHardware control-flow integrity: Intel CET, Arm PAC and BTINeeds hardware and OS support to take effect
-fsanitize=cfiRelease buildsIndirect-call type confusionClang, with LTO
-Wl,-z,relro,-z,now, -pieLinkRead-only relocations, full RELRO, ASLRDistribution defaults on most Linux targets
-fno-delete-null-pointer-checksKernel-style buildsKeeps null checks the optimiser would otherwise removeUsed where address zero is mappable and the standard’s assumption does not hold

Sanitizers above the rule, hardening flags below. Availability and spelling differ between GCC, Clang and MSVC; the forms shown are the GCC and Clang spellings. Source: GCC and LLVM documentation and published distribution hardening guidance.

# Two builds, one source tree. The first finds bugs, the second survives them.
cc -std=c23 -O1 -g -fsanitize=address,undefined -fno-omit-frame-pointer -o app-test *.c
cc -std=c23 -O2 -D_FORTIFY_SOURCE=3 -fstack-protector-strong \
   -ftrivial-auto-var-init=zero -fcf-protection=full -pie -Wl,-z,relro,-z,now -o app *.c

Back to top

Sources

  • ISO/IEC 9899:2024, Programming languages — C, with public working draft N3220 for clause numbering: §6.4.1 keywords, §6.5 expressions, §6.7.6 declarators, §6.10 preprocessing, §7 the library, §7.23.6 formatted I/O, Annex J undefined and implementation-defined behaviour.
  • ISO/IEC 9899:1990, its 1995 amendment, 9899:1999, 9899:2011 and 9899:2018, for the per-revision keyword and header lists.
  • GCC documentation — invocation options, C dialect options, warning options, instrumentation options, and the C status page.
  • Clang and LLVM documentation — the user manual, the sanitizer manuals (Address, Undefined Behavior, Thread, Memory), -fbounds-safety, and the C status page.
  • Microsoft C/C++ documentation — compiler options and C language conformance.
  • Published distribution hardening guidance, for the release-build flag set.

Code examples are original. Where a table condenses a longer normative document, the caption names the clause so the full text can be consulted.

Back to top