C Cheatsheet - C Language Syntax & Pointer Reference

This reference is for developers writing C for embedded, OS, or performance-critical work, where unlike managed languages you own memory and pointer lifetimes. It covers data types and constants, control flow, pointers and arrays (where the footguns live), functions, manual allocation and freeing, file I/O, and the preprocessor. Entries are grouped by the build block you are working in, with notes flagging where behavior is easy to get wrong. After reading you should be able to decide when to use a pointer vs an array, manage malloc/free without leaks, and encode compile-time logic with #ifdef.

Languages·58 commands·Last updated 2026-07-21
cSystems ProgrammingPointersMemory ManagementCompilationSyntax

Typical Use Case

For C programmers writing systems, embedded or performance-sensitive code. The sheet pins down the places where C is most likely to bite: the relationship between pointers and arrays, where the array name decays to a pointer in most expressions but sizeof still reports the whole array; the four allocation functions and the risk of ignoring their return values; the difference between p.x and pp->x; and the preprocessor, macros and header guards. What you actually use daily is the combinations — check after malloc, null the pointer after free, prefer memmove when regions may overlap, parenthesise macro arguments, and keep the ifndef guard even where pragma once would do. File I/O and a segfault triage path close it out.

Data Types & Variables 8

int x = 10;
Integer variable
float pi = 3.14f;
Single-precision float
double e = 2.718;
Double-precision float
char c = 'A';
Character variable
char str[] = "Hello";
String (char array)
const int MAX = 100;
Constant
enum {RED, GREEN, BLUE};
Enum type
typedef int Integer;
Type alias

Control Flow 8

if (x > 0) { } else if (x < 0) { } else { }
Conditional branch
switch (x) { case 1: break; default: break; }
Multi-way branch
for (int i = 0; i < n; i++) { }
for loop
while (condition) { }
while loop
do { } while (condition);
do-while loop
break;
Break out of loop/switch
continue;
Skip current iteration
goto label;
Jump (use with care)

Pointers & Arrays 8

int *p;
Pointer declaration
int x = 10; int *p = &x;
Take an address
*p = 20;
Dereference
int arr[5] = {1, 2, 3, 4, 5};
Array initialization
int *p = arr;
Array name is the base address
*(p + i)
Pointer arithmetic (equals arr[i])
int **pp;
Double pointer
void *vp;
Generic pointer

Functions 6

int add(int a, int b) { return a + b; }
Function definition
void swap(int *a, int *b);
Function declaration (prototype)
int (*fp)(int, int) = add;
Function pointer
static int count = 0;
Static local variable
extern int globalVar;
Extern variable declaration
inline int square(int x) { return x * x; }
Inline function

Memory Management 7

int *p = (int *)malloc(sizeof(int) * 10);
Allocate memory dynamically
int *p = (int *)calloc(10, sizeof(int));
Allocate and zero-initialize
p = (int *)realloc(p, sizeof(int) * 20);
Reallocate memory
free(p);
Free memory
memset(p, 0, size);
Initialize memory
memcpy(dest, src, size);
Copy memory
memmove(dest, src, size);
Safe memory copy (overlap-safe)

Structs & Unions 6

struct Point { int x; int y; };
Struct definition
struct Point p = {1, 2};
Struct initialization
p.x = 10;
Access a member
struct Point *pp = &p; pp->x = 10;
Pointer access to a member
union Data { int i; float f; };
Union (shared memory)
typedef struct { int x; int y; } Point;
Struct alias

File I/O 8

FILE *fp = fopen("file.txt", "r");
Open a file (r/w/a/rb/wb)
fclose(fp);
Close a file
fscanf(fp, "%d", &x);
Formatted read
fprintf(fp, "%d\n", x);
Formatted write
fread(buf, size, count, fp);
Binary read
fwrite(buf, size, count, fp);
Binary write
fgets(buf, size, fp);
Read a line
fseek(fp, offset, SEEK_SET);
Seek within a file

Preprocessor 7

#include <stdio.h>
Include a system header
#include "myheader.h"
Include a custom header
#define MAX 100
Macro definition
#define SQUARE(x) ((x) * (x))
Macro function
#ifdef DEBUG ... #endif
Conditional compilation
#ifndef HEADER_H ... #define HEADER_H ... #endif
Header guard
#pragma once
Modern header guard

Parameter matrix

ParameterEffectExample
int x = 10;Declares a signed integer variable.int x = 10;
float pi = 3.14f;Single-precision float (about 7 significant digits).float pi = 3.14f;
double e = 2.718;Double-precision float (about 15 significant digits).double e = 2.718;
char c = 'A';Character variable whose value is its ASCII code.char c = 'A';
char str[] = "Hello";Character array terminated by \0.char str[] = "Hello";
const int MAX = 100;Read-only constant; assigning to it is a compile error.const int MAX = 100;
enum {RED, GREEN, BLUE};Names a batch of integer constants, incrementing from 0 by default.enum {RED, GREEN, BLUE};
typedef int Integer;Creates an alias for an existing type.typedef int Integer;
for (int i = 0; i < n; i++)Counting loop.for (int i = 0; i < n; i++) { }
while (condition)Test first, then execute.while (condition) { }
do { } while (condition);Execute first, then test—runs at least once.do { } while (condition);
switch (x) { case 1: break; default: break; }Multi-way branch; break prevents fall-through.switch (x) { case 1: break; default: break; }
int *p = &x;Takes an address and assigns it to a pointer.int x = 10; int *p = &x;
*p = 20;Dereference to write a value.*p = 20;
int arr[5] = {1,2,3,4,5};Array initialization.int arr[5] = {1, 2, 3, 4, 5};
int *p = arr;An array name is the address of its first element.int *p = arr;
*(p + i)Pointer arithmetic, equivalent to arr[i].*(p + i)
int **pp;Pointer to a pointer.int **pp;
void *vp;Generic pointer.void *vp;
void swap(int *a, int *b);Function declaration (prototype).void swap(int *a, int *b);
int (*fp)(int, int) = add;Assigning a function pointer.int (*fp)(int, int) = add;
static int count = 0;Static local variable that persists across calls.static int count = 0;
extern int globalVar;References an externally defined variable.extern int globalVar;
inline int square(int x)Inline function hint.inline int square(int x) { return x * x; }
(int *)malloc(sizeof(int) * 10)Dynamic allocation; NULL indicates failure.int *p = (int *)malloc(sizeof(int) * 10);
(int *)calloc(10, sizeof(int))Allocates and initializes to 0.int *p = (int *)calloc(10, sizeof(int));
(int *)realloc(p, size)Reallocates, possibly returning a new address.p = (int *)realloc(p, sizeof(int) * 20);
free(p);Frees memory, then set to NULL.free(p); p = NULL;
memset(p, 0, size)Fills memory byte by byte.memset(p, 0, size);
memcpy(dest, src, size)Memory copy; source and destination must not overlap.memcpy(dest, src, size);
memmove(dest, src, size)Memory copy that allows overlapping regions.memmove(dest, src, size);
struct Point { int x; int y; };Struct definition.struct Point { int x; int y; };
struct Point p = {1, 2};Struct initialization.struct Point p = {1, 2};
pp->x = 10;Accessing a member through a struct pointer.struct Point *pp = &p; pp->x = 10;
union Data { int i; float f; };Union; members share memory.union Data { int i; float f; };
typedef struct { int x; int y; } Point;Struct alias.typedef struct { int x; int y; } Point;
FILE *fp = fopen("file.txt", "r");Opens a file (r/w/a/rb/wb).FILE *fp = fopen("file.txt", "r");
fclose(fp);Closes the file and flushes the buffer.fclose(fp);
fscanf(fp, "%d", &x);Formatted read.fscanf(fp, "%d", &x);
fprintf(fp, "%d\n", x);Formatted write.fprintf(fp, "%d\n", x);
fread(buf, size, count, fp);Binary read.fread(buf, size, count, fp);
fwrite(buf, size, count, fp);Binary write.fwrite(buf, size, count, fp);
fgets(buf, size, fp);Reads one line into the buffer.fgets(buf, size, fp);
fseek(fp, offset, SEEK_SET);Positions the file at a specific offset.fseek(fp, 0, SEEK_SET);
#include <stdio.h>Includes a system header file.#include <stdio.h>
#define MAX 100Macro constant definition.#define MAX 100
#define SQUARE(x) ((x) * (x))Function-like macro; arguments must be parenthesized.#define SQUARE(x) ((x) * (x))
#ifdef DEBUG ... #endifConditional compilation.#ifdef DEBUG ... #endif
#ifndef H ... #endif / #pragma onceHeader guard.#ifndef HEADER_H ... #define HEADER_H ... #endif

Common pitfalls

Symptomsizeof returns a size several times larger than the element count, or far larger than expected.

CauseAn array name decays to a pointer to its first element in most expressions, but not under sizeof, which still yields the byte size of the whole array; applying sizeof to a pointer yields the pointer size.

FixWhen you need the array length, use sizeof(arr) / sizeof(arr[0]); if the array has already decayed to a pointer you must track the length separately and cannot rely on sizeof.

SymptomThe program crashes halfway through or prints a garbled address value.

CauseThe malloc return value is dereferenced without a null check, or the old pointer is still used after realloc fails (on failure realloc leaves the original memory intact but returns NULL, and overwriting the pointer leaks it).

FixAlways check malloc/calloc/realloc for NULL; when calling realloc, receive the result in a temporary pointer and assign it back only on success, keeping the original pointer on failure.

SymptomThe same pointer is used again after free, causing intermittent segmentation faults.

CauseThe pointer keeps its original address after being freed, becoming a dangling pointer; reading, writing, or freeing it again is undefined behavior.

FixSet p = NULL; immediately after free(p), treating free-and-null as a fixed pair.

SymptomA macro produces a wrong result for certain arguments, e.g. 1+1 is computed as 3 instead of 4.

CauseMacros are textual substitution; if arguments are not parenthesized, operator precedence after expansion is decided by the caller context.

FixParenthesize every argument and the whole body in the macro definition: #define SQUARE(x) ((x) * (x)).

SymptomAccessing a struct pointer with p->x fails to compile, or writing p.x by mistake yields garbage.

CauseWhen p is a pointer you must use the -> dereference operator; the . operator applies only to a struct variable itself.

FixAdopt one convention: use pp->x for pointers and p.x for variables, confirming the left-hand type before assigning.

SymptomA self-written header included by multiple source files triggers a flood of duplicate-definition errors.

CauseThe header lacks an include guard, so the same definitions get expanded repeatedly.

FixAdd #ifndef HEADER_H ... #define HEADER_H ... #endif at the top of the header, or use #pragma once.

SymptomRight after fwrite finishes, another program reading the file finds the tail missing.

CauseData is still buffered in the FILE stream and has not reached disk because fclose was not called to flush.

FixAlways call fclose(fp) when writing ends—it flushes the buffer before closing; call fflush first if you need the data on disk earlier.

SymptomA switch branch misses a break, so one case falls through into the next.

CauseC switch statements fall through by default unless there is an explicit break or return.

FixAdd break at the end of every case, including after default; if the fall-through is intentional, add a comment inside the branch explaining it.

Troubleshooting

  1. 1Confirm that dynamic allocation succeeded and check capacity.

    int *p = (int *)malloc(sizeof(int) * 10); if (p == NULL) { /* failure branch */ } int *q = (int *)calloc(10, sizeof(int));

    Check for NULL before use; the calloc variant additionally guarantees all-zero initialization, which suits buffers that need clearing. A NULL p means the allocation failed (out of memory, or an overflowed size computation), so abort the flow instead of dereferencing it.

  2. 2Clarify how arrays decay to pointers.

    int arr[5] = {1,2,3,4,5}; int *p = arr; sizeof(arr); sizeof(p); *(p + 2);

    sizeof(arr) returns the byte size of the whole array while sizeof(p) returns the pointer size; they differing proves decay has occurred. At that point *(p+2) still works, but the array length information is gone and bounds checking must be handled separately.

  3. 3Resize a buffer safely.

    int *tmp = (int *)realloc(p, sizeof(int) * 20); if (tmp != NULL) { p = tmp; }

    On failure realloc returns NULL and leaves the original memory untouched, so you must receive it in a temporary pointer and write it back only on success; writing p = realloc(p, ...) directly discards the original pointer on failure and leaks it.

  4. 4Tell memcpy and memmove apart.

    memcpy(dest, src, size); memmove(dest, src, size);

    memcpy assumes the two regions do not overlap; overlapping is undefined behavior. memmove copies to a temporary area first and then writes back—slightly costlier but overlap-safe. Always use memmove for shifts within an array.

  5. 5Access a struct safely through both value and pointer.

    struct Point p = {1, 2}; p.x = 10; struct Point *pp = &p; pp->x = 20;

    First confirm that pp and p refer to the same object, then access through -> and . respectively: if p.x changes after modifying through pp, the pointer is correct; if not, the initialization or the target is wrong.

  6. 6Complete the file-write loop.

    FILE *fp = fopen("file.txt", "w"); fprintf(fp, "%d\n", 42); fclose(fp);

    Opening, writing, and closing are all required, and fclose also flushes the buffer. Read the file back immediately with a separate piece of code to distinguish "it was never written" from "the buffer was not flushed".

  7. 7Verify that a macro expands as expected.

    #define SQUARE(x) ((x) * (x)) SQUARE(a + b)

    The preprocessor does pure text substitution, so SQUARE(a + b) should expand to ((a + b) * (a + b)). Check item by item whether every argument and the whole body are parenthesized; a single missing pair changes precedence for certain arguments.

Command Examples

Overlapping source and destination require memmove, not memcpy

memmove(dest, src, size); memcpy(dest, src, size);

When shifting a slice of a buffer, source and destination overlap by definition and memcpy behaviour is undefined. memmove picks the copy direction for you; it is the only correct choice here.

Never lose the original pointer when realloc fails

int *p = (int *)malloc(sizeof(int) * 10); int *tmp = (int *)realloc(p, sizeof(int) * 20); if (tmp == NULL) { /* p is still valid */ } else { p = tmp; }

Writing p = realloc(p, ...) overwrites the only live pointer with NULL on failure, leaking the original block irrecoverably. Assign to a temporary first, then move it into p on success.

Read a line with fgets instead of an unbounded read

char buf[128]; if (fgets(buf, sizeof(buf), fp) == NULL) { /* EOF or error */ } buf[strcspn(buf, "\n")] = '\0';

Passing sizeof(buf) gives fgets a hard length bound, so an over-long line is truncated instead of smashing the stack. fgets keeps the newline; strip it before treating the buffer as a whole line.

Parenthesize macro parameters or arguments get evaluated twice

#define SQUARE(x) ((x) * (x)) int i = 1; int r = SQUARE(i++);

A preprocessing-level trap: i++ appears twice in the expansion, so it runs twice and the code still looks correct. A static inline function avoids the whole class of bug.

Output

// r = 2 instead of the expected 4: i++ is expanded twice

A static local makes a call counter, scoped to one file

static int count = 0; count++;

static gives the variable full program lifetime and zero-initialisation, but the name is file-local. To count across a multi-file program you need an extern declaration, otherwise each translation unit keeps its own unrelated counter.

Common Pitfalls

  • Reading or writing p after free(p), or forgetting to null it out, produces use-after-free — the hardest class of crash because it does not reproduce reliably.
  • p = (int *)realloc(p, ...) loses the original pointer when the allocation fails and returns NULL; stage the result in a temporary first.
  • memcpy(dest, src, sizeof(arr)) copies the trailing \0 along with the bytes and leaves trailing garbage in the destination; use strlen or sizeof - 1.
  • Handling input into a fixed array without any length bound is the main source of stack buffer overflows; at minimum use fgets(buf, sizeof(buf), fp).
  • A header without #ifndef HEADER_H ... #endif compiles fine alone and then fails with redefinition the moment two .c files include it.
  • Dereferencing a malloc/calloc/realloc result without a NULL check segfaults under memory pressure and is very hard to reproduce.

Tips

  • Always check that malloc's return value is not NULL.
  • Set the pointer to NULL after free to avoid dangling pointers.
  • An array name decays to a pointer in most expressions, but sizeof(arr) is still the whole array size.

FAQ

What happens if I skip the NULL check after malloc?

You get undefined behaviour. A NULL return means the allocation failed — out of memory, or a size computation overflowed — and writing through it typically ends in a segmentation fault or, worse, silent corruption somewhere else. Check it: p = (int *)malloc(sizeof(int) * 10); if (p == NULL) { /* handle */ }. Use calloc(10, sizeof(int)) when you want zeroed memory, and remember that a failed realloc returns NULL, so never overwrite the original pointer with its result or you leak the old block.

Why should I set a pointer to NULL right after freeing it?

free(p) releases the block but does not modify p, so p becomes dangling — it still holds the address now owned by the allocator. Calling free(p) again is a double free, and *p = 1 writes into memory you no longer own; both are undefined behaviour. Writing p = NULL; immediately after free makes the fact visible in the pointer itself, so misuse crashes on NULL instead of silently corrupting data.

Once I pass an array to a function it becomes a pointer — does sizeof still work?

It depends on scope. After int *p = arr; the pointer only reaches elements indirectly via *(p + i). But inside the original scope sizeof(arr) still yields the byte size of the whole array, because an array name decays to a pointer only in most expressions — and passing it as an argument is one of them. Since a callee cannot recover the element count from a pointer, pass the length explicitly alongside void swap(int *a, int *b).

Why do headers need include guards, and when should I use pragma once?

When one header is pulled in transitively by several .c files, its contents get expanded repeatedly — duplicate definition errors at best, wasted space at worst. The traditional form is #ifndef HEADER_H ... #define HEADER_H ... #endif, which relies on macro state. #pragma once is a single line with clearer intent and is the modern recommendation. For new headers just use #pragma once, and when editing older files do not strip their macro guards without replacing them, or the duplication comes back.

How much memory does char str[] = "Hello" take, and why is it not a string type?

C has no real string type. "Hello" is simply six character values plus an implicit terminator: H e l l o \0, so char str[] = "Hello"; has sizeof(str) == 6. That trailing \0 is the only thing that makes the array behave like a string for fgets or fprintf("%s"). When you allocate one extra byte it is exactly that empty terminator, so any manual traversal must test for \0 or you will read past the end.

Official References

Each command links to its official documentation below, so you can verify the latest usage and read deeper.

Maintained by LaoHand

Publicly updated on Jul 21, 2026, continuously proofread against official docs.

Contact Us

Wrong command or description? Send us corrections, business inquiries or product feedback by email.

Contact Us