Skip to main content

Pointers: Addresses, Dereference, & and *

What This Concept Is

A pointer is a typed variable whose value is the address of another object. "Typed" is the important word: int * and char * both hold addresses, but the type tells the compiler how many bytes to read on a dereference and how to step through memory.

Two operators turn values into pointers and back:

  • &x produces the address of x (a pointer to x's type)
  • *p yields the object p points to (the "pointee"), reading sizeof(*p) bytes

A pointer declaration follows the pattern "when you dereference this, you get the stated type":

int   x = 42;
int *p = &x; /* *p is an int */
char *c = (char *)p; /* re-typed view; *c is a char */

Why It Matters Here

Almost everything in C that "passes something around" is either a value copy or a pointer. Function arguments in C are always passed by value; to let a function modify a caller's variable, you pass a pointer. To avoid copying a large object, you pass a pointer. Arrays decay to pointers in most contexts. Strings are pointers to characters. There is no getting around it.

Concrete Example

int x = 42;
int *p = &x;

Suppose x lives at address 0x1000. Then on a 64-bit machine with 4-byte int and 8-byte pointer:

Address   Content               Size
0x1000 0x0000002A (= 42) 4 bytes (this is x)
...
0x2000 0x0000000000001000 8 bytes (this is p, pointing to x)

Reading *p means "go to the address stored in p (which is 0x1000), read sizeof(int) = 4 bytes, interpret as int." That yields 42.

Writing *p = 100; means "write the int 100 into the 4 bytes at 0x1000." After this, x is 100.

Common Confusion / Misconception

"int *p, q; declares two pointers." No. It declares one pointer p and one plain int q. The * binds to the name, not to the type. Prefer int *p; int *q; on separate lines.

"&x is just some number I can do math on." It is a typed pointer. Casting it to int and back is implementation-defined at best. Use uintptr_t if you must store an address as an integer.

"NULL is zero." It represents a pointer that does not point at a valid object. Dereferencing it is undefined behavior, not zero-returning. On most OSes it reliably crashes, which is the best outcome.

How To Use It

For every pointer you write:

  1. State its type. "This is a pointer to one int" or "pointer to the first of many ints."
  2. State what it points to, and whether that object outlives the pointer.
  3. Never dereference a pointer you have not initialized.
  4. Check for NULL after allocation and before dereferencing pointers that can legitimately be null.

Check Yourself

  1. What is the difference between int *p and int p?
  2. If p has type int * and *p = 5;, what must be true for that write to be safe?
  3. What does &*p mean when p is a valid pointer?

Mini Drill or Application

Predict each printed line, then run:

#include <stdio.h>

int main(void) {
int x = 10;
int *p = &x;
printf("x = %d, *p = %d\n", x, *p);

*p = 99;
printf("x = %d, *p = %d\n", x, *p);

int y = 7;
p = &y; /* retarget p */
printf("x = %d, *p = %d\n", x, *p);
return 0;
}

Build: gcc -Wall -Wextra -o basic_ptr basic_ptr.c. Draw a memory diagram for the state after each statement before running. The diagram is the point, not the output.

Read This Only If Stuck