C Cheat Sheet
Quick reference for C: compiling with gcc/clang, types, printf formats, pointers, strings, memory and the preprocessor. Targets C17, with C23 additions marked "(C23)".
On this page
Compile & Run #
| Command | What it does |
|---|---|
gcc main.c -o app && ./app | Compile main.c into the program app and run it (clang takes the same flags) |
gcc -Wall -Wextra -std=c17 main.c -o app | Turn on the useful warnings and pick the C standard |
-std=c23 | Use C23 (GCC 14+, Clang 18+; GCC 13: -std=c2x) |
-g | Add debug info for gdb / lldb |
-O2 | Optimize for speed (use -O0 -g while debugging) |
-lm | Link the math library (sqrt, pow on Linux); put it after the source files |
gcc -c util.c | Compile only, produces util.o |
gcc main.o util.o -o app | Link object files into one program |
-fsanitize=address,undefined | Catch out-of-bounds, use-after-free and UB at run time |
Program Structure & Functions #
#include <stdio.h>
int add(int a, int b); // prototype (declaration)
int main(void) {
printf("%d\n", add(2, 3));
return 0; // 0 = success
}
int add(int a, int b) { return a + b; } // definition
| Code | What it does |
|---|---|
int main(int argc, char *argv[]) | Entry point with command-line arguments (argv[0] = program name) |
return EXIT_SUCCESS; | Exit code from <stdlib.h> (EXIT_FAILURE for errors) |
int f(void); | Takes no arguments (before C23, int f(); meant "unspecified arguments") |
static int helper(int x) | Function visible only inside this .c file |
void swap(int *a, int *b) | Arguments are copies; pass a pointer to change the caller's variable |
static int count = 0; (in a function) | Keeps its value between calls |
extern int g_total; | Declares a global that is defined in another file |
Types & Sizes #
| Type | What it is |
|---|---|
char | 1 byte; a character or small integer ('A') |
short / int | Usually 2 / 4 bytes |
long | 8 bytes on 64-bit Linux/macOS, 4 bytes on Windows |
long long | At least 8 bytes |
float / double | 4 / 8 byte floating point (use double by default) |
unsigned int | Only non-negative values (42u) |
int8_t int16_t int32_t int64_t | Exact-width integers from <stdint.h> |
uint8_t uint32_t uint64_t | Exact-width unsigned integers (bytes, registers, bit flags) |
size_t | Unsigned type for sizes and indexes (sizeof, strlen return it) |
bool | true / false (C17: #include <stdbool.h>; built-in keywords in C23) |
sizeof(int) / sizeof x | Size in bytes of a type / variable |
(double)a / b | Cast: convert before dividing |
constexpr int SIZE = 10; | True compile-time constant (C23; GCC 13+, Clang 19+) |
auto n = 5; / typeof(x) y; | Type inference / copy a type (C23) |
0b1010 / 1'000'000 | Binary literal / digit separators (C23) |
printf & scanf Format Specifiers #
| Specifier | Type |
|---|---|
%d / %i | int |
%u | unsigned int |
%ld / %lld | long / long long (%lu, %llu for unsigned) |
%zu | size_t (result of sizeof, strlen) |
%f | double in printf (float is promoted to it); float in scanf |
%lf | double in scanf (required); same as %f in printf |
%e / %g | Scientific notation / shortest of %f and %e |
%c | char |
%s | String (char *) |
%p | Pointer address; cast to (void *) |
%x / %X / %o | Hexadecimal (lower / upper case) / octal |
%% | A literal % sign |
%5d / %-5d / %05d | Width 5: right-aligned / left-aligned / zero-padded |
%.2f | 2 digits after the decimal point |
"%" PRId64 | Print int64_t portably (<inttypes.h>; also PRIu32 etc.) |
scanf("%d", &n) == 1 | Read an int; pass the address and check how many items were read |
scanf("%19s", name) | Read a word of at most 19 chars into char name[20] (no & for arrays) |
Operators #
| Operator | What it does |
|---|---|
+ - * / % | Arithmetic; 7 / 2 is 3 (integer division), 7 % 2 is 1 |
7 / 2.0 | 3.5: one floating operand gives floating division |
++i / i++ | Increment, returns the new / old value |
== != < > <= >= | Comparison, gives 1 or 0 |
&& || ! | Logical and / or / not (short-circuit) |
a & b / a | b / a ^ b / ~a | Bitwise and / or / xor / not |
a << n / a >> n | Shift left / right by n bits |
flags |= 1u << 3 | Set bit 3 |
flags &= ~(1u << 3) | Clear bit 3 |
flags ^= 1u << 3 | Toggle bit 3 |
(flags >> 3) & 1u | Read bit 3 |
cond ? a : b | a if cond is true, otherwise b |
Control Flow #
if (x > 0) { ... } else if (x == 0) { ... } else { ... }
for (int i = 0; i < n; i++) { if (i == 2) continue; if (i == 8) break; }
while (running) { ... }
do { ... } while (again); // runs at least once
switch (cmd) {
case 'q': quit(); break; // without break it falls through
case 'h': case '?': help(); break;
default: puts("unknown");
}
Arrays & Strings #
| Code | What it does |
|---|---|
int a[5] = {1, 2, 3}; | Array of 5; the rest is zero-filled |
sizeof a / sizeof a[0] | Number of elements (only where a is a real array, not a pointer) |
int grid[3][4]; | 2D array: 3 rows of 4 |
char s[] = "hi"; | Modifiable string: {'h', 'i', '\0'} |
const char *s = "hi"; | Pointer to a read-only string literal |
strlen(s) | Length without the '\0' (needs <string.h>) |
strcmp(a, b) == 0 | Strings are equal (a == b only compares addresses) |
snprintf(buf, sizeof buf, "%s-%d", name, id) | Safe formatted write; always '\0'-terminates and truncates |
strcpy(dst, src) | Copy; overflows if dst is too small |
strncpy(dst, src, n) | Adds no '\0' when src is too long: set dst[n - 1] = '\0' yourself |
strcat(dst, src) | Append; overflows if there is no room |
strchr(s, 'c') / strstr(s, "sub") | Find a char / substring, returns a pointer or NULL |
strtol(s, &end, 10) | String to long with error checking (atoi has none) |
memcpy(dst, src, n) / memset(p, 0, n) | Copy / fill n bytes |
Pointers #
| Code | What it does |
|---|---|
int *p = &x; | p holds the address of x |
*p = 10; | Change x through the pointer (dereference) |
int *p = NULL; | Points to nothing; check before dereferencing (nullptr in C23) |
p + 1 | Next element (moves by sizeof *p bytes) |
p[i] | Same as *(p + i) |
q - p | Number of elements between two pointers (ptrdiff_t, print with %td) |
int **pp = &p; | Pointer to a pointer; **pp is x |
const int *p / int *const p | Can't change the value / can't change where p points |
void * | Generic pointer (returned by malloc); converts to any object pointer |
p->field | Member of the struct p points to, same as (*p).field |
int (*op)(int, int) = add; | Function pointer; call it with op(2, 3) |
qsort(arr, n, sizeof arr[0], cmp) | Library sort that takes a comparison function pointer |
Structs, Unions & Enums #
| Code | What it does |
|---|---|
typedef struct { char name[32]; int age; } Person; | Struct type you can use as just Person |
Person p = {.name = "Ann", .age = 30}; | Designated initializers; missing fields become 0 |
p.age / ptr->age | Access a field directly / through a pointer |
struct Node { int value; struct Node *next; }; | Self-referencing struct (needs the tag name) |
Person a = b; | Copies all fields (but == doesn't work on structs) |
union Value { int i; float f; }; | All members share the same memory; size of the largest |
enum Color { RED, GREEN, BLUE }; | Named integer constants 0, 1, 2 |
enum { LOW = 5, HIGH }; | Set values explicitly; HIGH becomes 6 |
enum Color : uint8_t { ... }; | Fixed underlying type (C23) |
Dynamic Memory #
| Code | What it does |
|---|---|
int *a = malloc(n * sizeof *a); | Allocate n ints, uninitialized (<stdlib.h>) |
if (a == NULL) { ... } | Always check: allocation can fail |
int *a = calloc(n, sizeof *a); | Allocate n ints set to zero |
int *tmp = realloc(a, m * sizeof *a); | Resize; may move the block. Then if (tmp) a = tmp; |
a = realloc(a, ...) | Leaks the old block if realloc fails and returns NULL |
free(a); a = NULL; | Release memory and avoid a dangling pointer (free(NULL) is safe) |
| Memory leak | Forgetting free; find it with valgrind ./app or -fsanitize=address |
| Use after free / double free | Touching or freeing memory again after free: undefined behaviour |
File I/O #
FILE *fp = fopen("data.txt", "r");
if (fp == NULL) { perror("data.txt"); return 1; }
char line[256];
while (fgets(line, sizeof line, fp)) { // NULL at end of file
line[strcspn(line, "\n")] = '\0'; // strip the newline fgets keeps
printf("%s\n", line);
}
fclose(fp);
| Code | What it does |
|---|---|
"r" / "w" / "a" | Read / write (creates or empties the file) / append |
"r+" / "rb" / "wb" | Read and write / binary mode (matters on Windows) |
fprintf(fp, "%d\n", x) | Formatted write to a file (stderr for error messages) |
fputs("text\n", fp) / fgetc(fp) | Write a string / read one char (EOF at the end) |
fread(buf, sizeof buf[0], n, fp) | Read n items of binary data (fwrite to write) |
perror("msg") | Print msg plus the reason for the last error |
fclose(fp) | Close and flush; always do it |
Preprocessor #
| Code | What it does |
|---|---|
#include <stdio.h> / #include "util.h" | Insert a system header / your own header |
#define MAX 100 | Text replacement constant (no =, no ;) |
#define SQUARE(x) ((x) * (x)) | Function-like macro; wrap every argument and the whole body in parentheses |
#define ARRAY_LEN(a) (sizeof(a) / sizeof((a)[0])) | Classic element-count macro |
#ifdef DEBUG ... #endif | Compile code only when DEBUG is defined (gcc -DDEBUG) |
#ifndef UTIL_H / #define UTIL_H / #endif | Include guard: the header is processed only once |
#pragma once | Shorter include guard (non-standard but supported everywhere) |
__FILE__ __LINE__ __func__ | Current file, line, function name (for log messages) |
static_assert(sizeof(int) == 4, "need 32-bit int"); | Compile-time check (C11; message optional in C23) |
Undefined Behaviour & Pitfalls #
| Mistake | What goes wrong |
|---|---|
if (x = 5) | Assigns instead of comparing; use == (-Wall warns) |
int x; printf("%d", x); | Reading an uninitialized local variable gives garbage |
a[5] on int a[5] | Out of bounds: valid indexes are 0 to 4 |
INT_MAX + 1 | Signed overflow is undefined; unsigned wraps around |
i = i++ + 1; | Modifying a variable twice in one expression |
char *s = "hi"; s[0] = 'H'; | Writing to a string literal crashes or misbehaves |
scanf("%d", n) | Missing &: scanf writes to a random address |
gets(buf) | Removed in C11; use fgets(buf, sizeof buf, stdin) |
sizeof arr on a parameter | Array parameters are pointers: pass the length separately |
return local_array; | Pointer to a local variable is invalid once the function returns |
#define DOUBLE(x) x * 2 | DOUBLE(1 + 2) is 1 + 2 * 2 = 5; add parentheses |
Tips #
- Always compile with
-Wall -Wextraand fix every warning; most beginner bugs show up there first. - During development add
-g -fsanitize=address,undefined: it turns silent memory bugs into clear error reports with line numbers. - Pass the buffer size with every buffer (
fgets(buf, sizeof buf, fp),snprintf(buf, sizeof buf, ...)) and prefer them overgets,strcpyandsprintf. - Every
mallocneeds exactly onefree, everyfopenonefclose; decide which function owns the memory. - Use
<stdint.h>types likeuint8_tanduint32_twhen the exact size matters (file formats, embedded registers, network data).
Nothing on this sheet matches “”.