C CLP study materials
C CLP study resources

C CLP cheat sheet

C CLP · Keep the key ideas within reach.

Quick reference

Program Structure, Compilation, and Preprocessing

  • Translation phases run in order: trigraph replacement, line splicing (backslash-newline), tokenization, preprocessing directives, escape conversion, string concatenation, then compilation proper.

  • The preprocessor operates on preprocessing tokens before any type information exists; macros are pure token substitution and have no scope or type checking.

  • Object-like macro #define N 10 substitutes tokens; function-like macro #define SQ(x) ((x)*(x)) requires parentheses around parameters and the whole body to avoid precedence bugs.

  • # stringizes a macro parameter and ## pastes tokens; both are only valid inside function-like macro replacement lists.

  • #include "file" searches implementation-defined locations (typically the current directory first); #include <file> searches system include paths.

  • Include guards (#ifndef H / #define H / #endif) or #pragma once prevent multiple inclusion within one translation unit.

  • Conditional compilation uses #if, #ifdef, #ifndef, #elif, #else, #endif; defined(X) is only meaningful inside #if/#elif expressions.

  • The predefined macros __FILE__, __LINE__, __DATE__, __TIME__, __STDC__ expand at preprocessing time; __func__ is a function-scope identifier, not a macro.

  • Separate compilation: each .c file plus included headers forms a translation unit; the linker resolves external symbols across object files, so a function declared but never defined yields a link error, not a compile error.

Quick reference

Data Types, Declarations, and Type Conversions

  • Core types: char, short, int, long, long long (signed/unsigned), float, double, long double, plus _Bool; sizes are implementation-defined except minimum ranges guaranteed by the standard.

  • Integer conversion rank orders char < short < int < long < long long; usual arithmetic conversions promote operands to a common type based on rank and signedness.

  • Integer promotions convert any type of rank below int to int (or unsigned int if int cannot represent all values).

  • Assigning a signed negative value to an unsigned type is defined: it wraps modulo 2^N; signed overflow is undefined behavior.

  • const makes an object read-only after initialization; volatile forces every access to actually occur and forbids caching in registers.

  • typedef creates a type alias, not a new type; it does not introduce a tag namespace entry.

  • Declarations vs definitions: a declaration introduces a name; a definition allocates storage or supplies a function body. extern int x; declares; int x; at file scope is a tentative definition.

  • sizeof yields size_t (an unsigned type) and does not evaluate its operand when the type is not a variable-length array.

Quick reference

Operators, Expressions, and Evaluation Order

  • Precedence (high to low): postfix () [] . ->; unary ! ~ + - * & ++ -- sizeof; cast; multiplicative; additive; shift; relational; equality; bitwise AND, XOR, OR; logical AND, OR; conditional ?:; assignment; comma.

  • && and || short-circuit and introduce sequence points; the right operand is not evaluated when the result is already determined.

  • The , operator evaluates left then right and yields the right value; it is a sequence point. It is distinct from the comma separating function arguments.

  • Unsequenced modification of the same scalar between sequence points (e.g. i = i++) is undefined behavior.

  • &, |, ^ do not short-circuit and require integer operands; ~ is bitwise complement, ! is logical negation.

  • Shift counts negative or >= the operand width are undefined; right shift of a negative signed value is implementation-defined.

  • The conditional operator a ? b : c evaluates only one of b or c; usual arithmetic conversions apply to b and c.

  • Assignment operators are right-associative; a = b = c assigns c to b then b to a.

Quick reference

Control Flow and Statements

  • if/else binds else to the nearest unmatched if; braces are needed to control grouping.

  • switch requires an integer expression; case labels must be integer constant expressions, and default appears at most once.

  • switch falls through unless a break or return is reached; break exits the switch, not an enclosing loop.

  • for (init; cond; step): init runs once, cond is checked before each iteration, step runs after each body execution; continue jumps to the step expression.

  • while and do ... while differ: do executes the body at least once and its condition is tested after the body.

  • break exits the innermost loop or switch; continue skips to the next iteration of the innermost loop.

  • goto jumps to a label within the same function; it cannot cross function boundaries and cannot jump into a variable-length array's scope.

  • A null statement is just ;; an empty body after while(...); is a common logic error.

Quick reference

Functions, Scope, Storage Duration, and Linkage

  • Functions are pass-by-value in C; to modify a caller's object, pass a pointer and dereference inside the function.

  • Default argument promotions apply to arguments of prototypes with ... (variadic) or to functions declared without a prototype: float promotes to double, small integers promote to int.

  • static at file scope gives internal linkage; static at block scope gives static storage duration with no linkage.

  • extern declares a name with external linkage; a definition without extern at file scope has external linkage by default.

  • Block scope begins at declaration and ends at the closing brace; file scope extends to end of translation unit; function prototype scope covers only the parameter list.

  • Storage durations: automatic (block variables), static (file-scope and static locals), thread (_Thread_local), allocated (from malloc).

  • register is a hint only; taking the address of a register object is a constraint violation.

  • A function with no parameters should be declared f(void); f() means unspecified parameters in older-style declarations.

Quick reference

Pointers and Arrays

  • An array name in an expression (except as operand of sizeof, &, or a string literal initializer) decays to a pointer to its first element.

  • Pointer arithmetic is scaled by the pointed-to type: p + 1 advances sizeof(*p) bytes; only pointers into the same array (or one past the end) may be subtracted.

  • a[i] is defined as *(a + i); i[a] is therefore equivalent.

  • &a yields a pointer to the whole array with type T (*)[N]; a decays to T *.

  • A pointer to void converts to and from any object pointer type without a cast; function pointers are not convertible to void * in strictly conforming code.

  • NULL is a null pointer constant; dereferencing it is undefined behavior. Compare pointers with ==/!= rather than relational operators except within an array.

  • Multidimensional arrays are arrays of arrays; int a[3][4] passes as int (*)[4], not int **.

  • Pointer to function: int (*fp)(int); calling through a null or dangling function pointer is undefined behavior.

Quick reference

Strings and Character Handling

  • A C string is a char array terminated by a null character '\0'; the terminator is not counted by strlen but must fit in the buffer.

  • strlen returns size_t and scans until the terminator; it does not include '\0' in the count.

  • strcpy/strcat do not bounds-check; strncpy may leave no terminator and pads with zeros; snprintf always terminates if size > 0.

  • strcmp returns negative, zero, or positive for lexicographic comparison of unsigned char values; strncmp limits the comparison length.

  • <ctype.h> functions (isdigit, isalpha, toupper, etc.) require an argument representable as unsigned char or EOF; passing a negative char is undefined behavior.

  • String literals are arrays of char with static storage duration; modifying a literal (via a non-const pointer) is undefined behavior.

  • Adjacent string literals are concatenated during translation phase 6, before compilation.

  • memcpy copies non-overlapping regions; memmove handles overlapping regions; both take size_t byte counts and do not stop at '\0'.

Quick reference

Dynamic Memory Management

  • malloc(size) returns uninitialized storage or NULL; calloc(n, size) zero-initializes and may overflow-check; realloc may move the block and frees the old pointer on success.

  • free(NULL) is a no-op; freeing a pointer not returned by an allocation function, or freeing twice, is undefined behavior.

  • After free(p), p is indeterminate (dangling); setting p = NULL after freeing prevents accidental reuse.

  • realloc(p, 0) is implementation-defined: it may free p and return NULL, or return a unique pointer.

  • malloc returns memory suitably aligned for any object type; casting its return value is unnecessary in C and can hide a missing prototype error.

  • Allocated storage has no declared type until written; effective type rules govern reads of the stored bytes.

  • Every successful allocation must be matched by exactly one free; leak detection requires tracking ownership across functions.

  • alloca is non-standard and frees on function return; standard C offers only malloc family and aligned_alloc for aligned storage.

Quick reference

Structures, Unions, Enumerations, and Bit-fields

  • Struct members are laid out in declaration order with possible padding; sizeof(struct) may exceed the sum of member sizes.

  • offsetof(type, member) from <stddef.h> gives the byte offset of a member.

  • A struct may be assigned, passed, and returned by value; it may not be compared with ==.

  • A pointer to a struct and a pointer to its first member have the same address; a cast between them is guaranteed to work.

  • Unions overlay members in shared storage; reading a member other than the last written is implementation-defined (type punning via union is common but not strictly portable).

  • Enumeration constants have type int and are integer constant expressions; the underlying type of the enum itself is implementation-defined.

  • Bit-fields are declared as type name : width; width must not exceed the bit width of the type, and taking the address of a bit-field is not allowed.

  • Bit-field layout, ordering, and padding are implementation-defined; use masks and shifts for portable bit manipulation.

Quick reference

Standard Library Usage and File I/O

  • FILE * streams are opened with fopen(path, mode) where mode includes "r", "w", "a", "r+", "w+", "a+", optionally with b; check for NULL on failure.

  • fclose flushes buffered output and releases the stream; failing to close can lose buffered data.

  • fgetc/fputc handle single characters; fgets reads at most n-1 characters plus a terminator; fread/fwrite transfer fixed-size records.

  • fseek(stream, offset, whence) with SEEK_SET, SEEK_CUR, SEEK_END; ftell returns the current position as long.

  • feof only reports end-of-file after a read attempt has failed; it is not a predictive test.

  • printf format specifiers: %d, %u, %ld, %zu for size_t, %f, %e, %g, %c, %s, %p, %%; mismatched types are undefined behavior.

  • scanf returns the number of items successfully assigned; %s reads a whitespace-delimited token and does not bounds-check without a width, e.g. %9s.

  • <stdlib.h> provides atoi, strtol (with end pointer and base), qsort (comparator returns negative/zero/positive), and bsearch; exit flushes and closes all streams, _Exit does not.