C for People Who Already Know How to Code
Pointers and Arrays
Arrays decay, pointers don't grow back
An array expression, almost anywhere it's used, silently converts to a pointer to its first element. This is called array decay, and the classic place it bites is sizeof:
void f(int arr[10]) {
printf("%zu\n", sizeof(arr)); // prints 8 (or 4): sizeof(int*), NOT 40
}
int main(void) {
int arr[10];
printf("%zu\n", sizeof(arr)); // prints 40: the real array size, here in its defining scope
}int arr[10] as a function parameter is quietly rewritten by the compiler to int *arr. The [10] is documentation, not enforcement. Any bound checking has to be passed in separately (an explicit length parameter) because the function has no way to recover it from the pointer.
Try it yourself: watch sizeof change the moment an array crosses a function boundary›
Write a function taking int arr[10] and print sizeof(arr) inside it, then print sizeof of the same array in main. Confirm the numbers don't match, and that -Wall doesn't warn you about it. This one is by design, not a bug the compiler can catch.
Pointer arithmetic only has meaning inside (and one past) an array
ptr + 1 is only defined if ptr points within an array (or one element type) and ptr + 1 lands within the array or exactly one element past its end. Computing anything further, even without dereferencing it, is undefined behavior:
int arr[5];
int *p = arr + 5; // legal: one-past-the-end, may be compared but not dereferenced
int *q = arr + 6; // UB the moment this pointer is *computed*, even if you never touch *qMulti-dimensional arrays vs arrays of pointers
int grid[3][4] is one contiguous 48-byte block. int *grid[3] is three separately-allocated rows reachable through an array of pointers. They're both "2D" in casual conversation and completely different in memory, which matters the moment you write a function signature for one:
// correct: the compiler needs the inner dimension to compute row strides
void sum_rows(int arr[][4], int rows) {
for (int i = 0; i < rows; i++)
for (int j = 0; j < 4; j++)
/* arr[i][j] */;
}
// this does NOT work for a true 2D array, it's the signature for an array of pointers
void wrong(int **arr, int rows);Reading pointer declarations right to left
const next to a pointer means different things depending on which side of the * it's on. Read the declaration starting from the variable name, moving outward:
const int *p: pointer to a const int. You can repointp, you can't modify*p.int *const p: const pointer to an int. You can modify*p, you can't repointp.const int *const p: both. Can't repoint, can't modify what it points to.
Function pointers, briefly
A typedef makes these tolerable to read, and they're the backbone of any callback or dispatch-table pattern in C: there's no closures, no first-class functions, just addresses of functions with a matching signature:
typedef int (*binop_fn)(int, int);
int add(int a, int b) { return a + b; }
binop_fn op = add;
int result = op(2, 3); // 5Try it yourself: build a tiny opcode dispatch table›
Define binop_fn add, sub, mul functions, put them in an array indexed by an enum { OP_ADD, OP_SUB, OP_MUL }, and call through table[op](a, b) instead of writing a switch. This pattern is how a lot of real interpreters and VMs dispatch instructions.