Where we are, and why pointers waited
Back in the Learning-C rung we deliberately stopped before pointers, and promised them a rung of their own. This is that rung. Everything you have learned still holds: a variable is a named, typed box in memory; the bits in that box mean nothing until a type reads them; and memory itself, from the very first foundations guide, is one long numbered array of bytes. Hold onto that last picture especially hard, because a pointer is nothing more than a way of naming a position in that array and carrying it around as a value.
So why the warning labels, why a whole rung? Because once you can name a memory position, you can also name the wrong one, name one that no longer exists, or walk off the end of what you meant. Pointers are not hard because the idea is deep — the idea is almost embarrassingly simple. They are hard because they are unforgiving: a small slip becomes a crash or, worse, silent corruption. This rung's job is to make the simple idea genuinely clear first, in this guide, and then to walk the failure modes honestly across the next four. We start with the happy, solid ground.
Memory has addresses, the way houses have numbers
Picture the street of mailboxes again, but now read the numbers painted on them. Each byte of memory sits at a numbered position, and that number is its address. The whole set of legal addresses your program can use is its address space — on a modern 64-bit machine that is an enormous range, numbered from 0 up to nearly 2^64. We always write addresses in lowercase hex, so a byte might live at 0x1F or at 0x7fffffffe3c0. The address is just where; what is stored there is a separate question entirely.
A value that occupies several bytes — an `int` is usually 4 bytes — does not have four addresses; by convention it has one, the address of its first (lowest) byte. When we say a variable `x` is "at" 0x1000, we mean its bytes begin there: 0x1000, 0x1001, 0x1002, 0x1003. The compiler is the one handing out these addresses, deciding where each of your variables lives. You normally never see them — until you ask. And the way you ask, in C, is a single character.
int x = 42; address bytes (little-endian) meaning 0x1000 2a 00 00 00 int x = 42 (0x2a == 42) 0x1004 .. the next variable... &x == 0x1000 <- the address OF x x == 42 <- the value AT that address
& takes an address, * follows it
Two operators do all the work, and they are exact opposites. The address-of operator, written as a single ampersand before a variable, asks the compiler "where does this box live?" So &x is not the value 42; it is the address 0x1000 where that 42 sits. Read `&x` aloud as "the address of x." That address is itself a value — a number — and like any value it can be stored. The box you store it in is a pointer.
Declaring a pointer uses a star in the type: `int *p;` reads as "p is a pointer to an int." Assign `p = &x;` and now `p` holds 0x1000 — it points at `x`. To go the other way, from the address back to the value living there, you use the dereference operator, also a single star but now in front of the pointer: *p means "the int that p points to," which is 42. The symmetry is the whole story: `&` turns a value into its address, `*` turns an address back into the value. They undo each other.
int x = 42; int *p = &x; /* p holds the address of x */ p == 0x1000 (the address) *p == 42 (the value at that address) *p = 99; /* writes through p: now x == 99 too */
Why a pointer has a type at all
Here is a fair question: if a pointer just holds an address, and addresses are all plain numbers, why isn't there one kind of pointer? Why do we write `int *p` and `char *s` instead of one universal pointer? The answer is that an address alone tells you where but not how to read what is there — and reading bytes correctly is the same pattern-versus-interpretation idea from the data rung. The pointer's type supplies the missing half: it says how many bytes the target occupies and how to interpret them.
Concretely, when you write `*p` on an `int *`, the compiler reads 4 bytes starting at that address and treats them as an `int`; dereference a `char *` at the same address and it reads just 1 byte as a `char`. Same address, different lens, different value. This is also why, in the next guide, pointer arithmetic will move in units of the pointed-to type rather than in raw bytes. A pointer's type is not decoration; it is the instruction for how to use the address it carries.
There is one deliberate exception worth naming now: a null pointer, written as the constant `NULL` (or just 0 in a pointer context). It is a pointer that, by guarantee, points at nothing — a reserved value, distinct from every real address, that means "I am not aimed anywhere valid." You use it to say "this pointer has no target yet," and you check for it before following a pointer. We will lean on null heavily; for now just register that it exists and that dereferencing it is one of the classic ways a program dies.
When a pointer goes wrong
Now the honest part, because hiding it would make you a more dangerous programmer, not a safer one. A pointer is only as good as the address inside it. If `p` holds a number that is not the address of anything you own — a null, a stale address from a box that no longer exists, or pure garbage from an uninitialized pointer — then `*p` reaches into a place you have no right to touch. When the address falls outside what the operating system granted your program, the hardware notices and the kernel kills you with a segmentation fault: the blunt message `Segmentation fault (core dumped)` that every C programmer eventually learns to read as "I dereferenced a bad pointer."
Worse than the crash is the case where there is no crash. If the bad address happens to land inside memory you do own — just not the box you meant — then `*p = 99` silently overwrites something else, and the program limps on with corrupted data until it misbehaves far from the real cause. A segfault is loud and findable; silent corruption is the nightmare. This is exactly why the next four guides exist: pointer arithmetic that can step off the end, dangling pointers to boxes that have gone away, and the discipline that keeps every one of them aimed at something real.
Where this leaves you
Step back. You now know the one idea the entire rung rests on: a pointer is a value that holds an address, `&` produces an address from a variable, and `*` follows an address back to its value. That is genuinely all a pointer is — there is no deeper magic waiting. Everything ahead is this same idea applied with care: making the address point at the right thing, keeping it pointing there, and reading or writing through it only while the target truly exists.
The next guide picks up exactly where this one ends. We took `&` and `*` slowly here; guide 2 puts them to work — dereferencing in earnest, the null pointer as a discipline rather than just a fact, and pointer arithmetic, the surprising-at-first rule that adding 1 to an `int *` moves it forward by a whole `int`, not a single byte. Once that clicks, guide 3 reveals the truth about arrays and pointers, and the rung climbs from there toward the stack, the heap, and the failure modes we have only glimpsed. You have the foundation; now we build on it.