A structured lesson workspace with readable content, hands-on examples, and a clean path to completion.
A pointer is a variable that holds a memory address. Not a value. An address. The address of another variable, of a heap allocation, of a function, of a memory-mapped hardware register. Pointers are how C gives you direct access to the machine's memory model. They are also how you shoot yourself in the foot, corrupt data, create security vulnerabilities, and spend six hours debugging a segfault. Mastering pointers is the single most important step in learning C. Everything after this builds on them.
The address-of operator & gives you the address of a variable. The dereference operator * gives you the value at an address. They are inverses. If p points to x, then *p is x. If you have x, then &x is the address that p would hold.
#include <stdio.h>int main(void) {int x = 42;int *p = &x;printf("x = %d\n", x);printf("&x = %p\n", (void *)&x);printf("p = %p\n", (void *)p);printf("*p = %d\n", *p);printf("&p = %p\n", (void *)&p);*p = 99;printf("\nAfter *p = 99:\n");printf("x = %d\n", x);printf("*p = %d\n", *p);return 0;}
p and &x print the same address. They are the same location. p itself lives at a different address (&p), because p is its own variable occupying its own 8 bytes on a 64-bit system. Writing *p = 99 modifies x. Not p. The thing p points to. This indirection is the entire mechanism.
On a 64-bit system, every pointer is 8 bytes regardless of what it points to. int*, char*, double*, struct Foo* are all 8 bytes. The type does not affect the pointer's size. It affects how pointer arithmetic works and how dereferencing interprets the bytes at the target address. int* says "the address I hold points to 4 bytes that should be read as an int." char* says "1 byte, read as a char."
#include <stdio.h>int main(void) {printf("sizeof(int*): %zu\n", sizeof(int*));printf("sizeof(char*): %zu\n", sizeof(char*));printf("sizeof(double*): %zu\n", sizeof(double*));printf("sizeof(void*): %zu\n", sizeof(void*));return 0;}
#include <stdio.h>int main(void) {int *p;printf("Uninitialized pointer: %p\n", (void *)p);printf("Dereferencing: %d\n", *p);return 0;}
The uninitialized pointer holds whatever garbage was in that stack location. Dereferencing it reads or writes an arbitrary address. Sometimes that address is mapped and you get silent corruption. Sometimes it is unmapped and you get a segfault. The segfault is the good outcome. It tells you immediately. The silent corruption is the bad outcome. It tells you three days later, in production, in a different function.
Initialize every pointer at declaration. Point it at a valid variable, or set it to NULL. A NULL pointer dereference crashes deterministically. A garbage pointer dereference corrupts silently. Deterministic crashes are debuggable. Silent corruption is not.
Pointers are not hard. They are simple. What is hard is keeping track of what each pointer points to, when that target is valid, and who is responsible for freeing it. Those questions drive the rest of this module. Next: pass by reference, where pointers let functions modify their caller's variables.
Finish the lesson once you have worked through the material. This awards ★ 30 XP.