Menu

Pointers in C: A Complete Guide With Examples

A pointer is a variable that stores a memory address. This page builds the idea from the ground up - the & and * operators, declaring and dereferencing pointers, why pointer types matter, and the swap() function that shows why pointers exist at all.

This page includes runnable editors - edit, run, and see output instantly.

Every variable in your program lives somewhere in memory, and every "somewhere" has a number - its address. A pointer is just a variable that stores one of those numbers. That is the entire idea. Everything else on this page is syntax and consequences.

The reason pointers matter so much in C is that C hands every function a copy of its arguments. A function that is given the number 7 can change its copy all it likes; the caller's variable never moves. Give the function an address instead, and it can reach back and change the original. Arrays, strings, dynamic memory, and every data structure you will ever build in C rest on that one capability.

Memory, Addresses, and Why Anything Has One

When you write int score = 42;, the compiler reserves a few bytes of memory and gives them a name you can use. Those bytes also have a numeric address. Think of memory as a very long street of numbered houses: score is the family living at house number 0x7ffd4c2a - the name is for you, the number is for the machine.

The & operator asks for that number:

Run it. The three addresses are different because the three variables occupy different bytes, and they will change from run to run - that is normal, and it is why you never hard-code an address. Note the (void *) cast: %p is specified to take a void *, and passing any other pointer type is technically undefined behavior. Make the cast a habit.

The addresses being unpredictable is not a problem, because you almost never care what an address is. You care that you have it.

Declaring a Pointer

A pointer declaration names the type it points to:

int    *p;      // p can hold the address of an int
double *q;      // q can hold the address of a double
char   *name;   // name can hold the address of a char

Read int *p; as "*p is an int" - dereferencing p gives you an int. That reading survives every complicated declaration C can throw at you later.

Where you put the * is a style choice the compiler ignores: int* p, int *p, and int * p all declare the same thing. Most C code writes int *p, and there is a practical reason:

int* a, b;   // a is a pointer to int; b is a PLAIN int, not a pointer
int *a, *b;  // both are pointers - the * binds to the name, not the type

The * attaches to the declarator, not to the type. Writing it next to the name keeps that visible.

& and *: The Two Operators

Two operators do all the work, and they are exact opposites.

  • &x - address-of. Produces the address where x lives.
  • *p - dereference. Goes to the address in p and gives you the object living there.

The last two lines are the whole point of pointers. Nothing assigned to age directly, yet age changed - because *p = 31 means "store 31 at the address p holds", and that address is age's address.

Here is the picture in memory:

      p                        age
 +------------+           +----------+
 | 0x7ffd1c40 | --------> |    31    |
 +------------+           +----------+
 at 0x7ffd1c38            at 0x7ffd1c40

 p    -> the address 0x7ffd1c40
 *p   -> the int stored there, 31
 &age -> 0x7ffd1c40, the same address p holds
 &p   -> 0x7ffd1c38, where p itself lives

Notice the last line: the pointer is itself a variable somewhere in memory, so it has its own address too. That is not a trick question, it is just consistency - and it is how pointers to pointers (int **) work.

Because & and * undo each other, *&age is simply age, and &*p is simply p.

Why Pointer Types Matter

A pointer stores an address, and an address is just a number - so why does the compiler insist on knowing whether it points to an int or a double? Two reasons:

  1. How many bytes to read. Dereferencing an int * reads 4 bytes (typically); dereferencing a double * reads 8. Without the type, *p would be meaningless.
  2. How to interpret those bytes. The same bit pattern is one number as an int and a completely different one as a float.

The size of the pointer itself, on the other hand, does not depend on what it points to - an address is an address:

On a 64-bit machine all three pointers are 8 bytes, while the things they point at are 4, 8, and 1. The pointed-to type is what makes *p and pointer arithmetic mean anything.

Assigning across pointer types is an error the compiler will complain about, and for good reason - pi = &d; would set you up to read 4 bytes of a double and call the result an integer.

The Canonical Motivation: swap()

Here is the function every C course reaches for, because it fails in exactly the way that explains pointers.

The function reports that it swapped them - and main reports that nothing happened. broken_swap received copies of x and y. It swapped its copies perfectly, then those copies ceased to exist when the function returned.

Hand it the addresses instead:

Now a and b are still copies - copies of two addresses - but a copy of an address points at the very same variable the original did. *a is x, wherever the function is called from.

This is the pattern behind every C function that modifies its caller's data, and behind function parameters generally: C has no pass-by-reference, so you pass a pointer and dereference it. It is also why scanf needs an &:

int n;
scanf("%d", &n);   // scanf must be able to write into n, so it needs n's address

Returning More Than One Value

A C function returns one value. Pointers are how you get around that: pass in addresses for the extra results.

The return value carries the status and the pointers carry the results. That split is everywhere in C's own standard library and in operating-system APIs.

Pointers to Pointers

Since a pointer is a variable, you can take its address, and the type of that address is "pointer to pointer to int":

  pp              p               value
+------+       +------+        +------+
| &p   | ----> | &val | -----> |   8  |
+------+       +------+        +------+

You will meet ** for real when a function needs to change a pointer the caller holds - for example, an allocator that sets the caller's pointer to fresh memory - and in the char *argv[] of command-line arguments.

Four Mistakes Worth Knowing Before You Make Them

Using a pointer that was never pointed anywhere. An uninitialized pointer holds whatever bytes were already there. Dereferencing it reads or writes a random address.

int *p;      // p holds garbage
*p = 10;     // undefined behavior - likely a crash

Initialize every pointer, with a real address or with NULL. See null pointers for the discipline around that.

Confusing *p with p. p = 5; sets the pointer to address 5 (nonsense); *p = 5; stores 5 where it points. The compiler will warn on the first, and warnings are worth reading.

Forgetting the & in scanf. scanf("%d", n) passes n's value as an address. It compiles with a warning and then writes to whatever memory that number names.

Returning the address of a local variable. The local is gone the moment the function returns; the address is a receipt for demolished property.

int *bad(void) {
    int local = 42;
    return &local;   // the caller gets a dangling pointer
}

If you need memory that outlives the call, allocate it - that is what dynamic memory is for.

Where to Go Next

Pointers do not stop at "hold one address". Adding to a pointer steps it through memory in units of its type, which is the subject of pointer arithmetic; and an array name in C decays into a pointer to its first element, which is why pointers and arrays are effectively the same topic once you know both. Read those two next, and the rest of C stops looking like magic.

Frequently Asked Questions

What is a pointer in C?

A pointer is a variable whose value is a memory address - the location of some other object in memory. int *p = &age; makes p hold the address of age, and *p then reads or writes the age variable through that address.

What is the difference between * and & in C?

&x is the address-of operator: it produces the address where x lives. *p is the dereference operator: it goes to the address stored in p and gives you the object there. They undo each other, so *&x is just x.

Why does the * appear twice - in the declaration and when using the pointer?

They are two different uses of the same symbol. In int *p; the * is part of the type, saying "p is a pointer to int". In *p = 5; the * is the dereference operator, saying "store 5 at the address p holds". Reading the declaration as "*p is an int" makes both make sense.

Why do I need pointers in C?

C passes every argument by value, so a function can never change a caller's variable unless it is handed the address. Pointers also let you walk arrays efficiently, return more than one result, build linked structures, and use memory allocated at runtime with malloc.

Coddy programming languages illustration

Learn to code with Coddy

GET STARTED