Pointers and Memory Addressing โ Revision Notes
Pointers are the differentiator in C interviews โ panels use them to separate candidates who memorised syntax from those who understand memory. Segmentation faults, pointer arithmetic, and const placement are perennial written-test and debugging questions.
What a pointer is
A pointer is a variable that stores the memory address of another object. &x gives the address of x; *p dereferences p to read/write the pointed-to value. A pointer's own size is fixed by the architecture (8 bytes on 64-bit), regardless of the type it points to.
Pointer arithmetic
Adding 1 to a pointer advances it by sizeof(pointed-type) bytes, not 1 byte. So for int *p, p + 1 moves 4 bytes ahead. This is why array indexing a[i] is exactly *(a + i).
const and pointers (read right-to-left)
| Declaration | Meaning |
|---|---|
const int *p |
pointer to const int (can't change *p) |
int *const p |
const pointer to int (can't change p) |
const int *const p |
both fixed |
Dangerous pointers
- NULL pointer: points to nothing; dereferencing crashes.
- Dangling pointer: points to freed/out-of-scope memory.
- Wild pointer: uninitialized, points anywhere.
Exam Tricks & Tips
- ๐ฏ
a[i]โก*(a + i)โก*(i + a)โกi[a]โ the last form actually compiles and is a classic trick question. - ๐ฏ A pointer's size depends on the architecture (8 bytes on 64-bit), not on what it points to โ
sizeof(char*) == sizeof(double*). - ๐ฏ After
free(p), setp = NULLto avoid a dangling-pointer double-free. - ๐ฏ Read
constdeclarations right-to-left from the variable name to decode what is constant. - ๐ฏ
void*is a generic pointer โ it can hold any address but must be cast before dereferencing, and pointer arithmetic on it is illegal in standard C. - ๐ฏ A double pointer (
int **pp) is needed to modify a caller's pointer inside a function (pass-by-reference of a pointer). - โ Common mistake: dereferencing an uninitialized or NULL pointer โ the classic cause of a segmentation fault.
Expected pattern
Output-prediction with pointer arithmetic on arrays, decoding const int *const p, or "why does this crash?" (dangling/NULL). Swap-two-numbers via pointers is a staple viva question.
Quick recap
A pointer stores an address; & takes an address, * dereferences. Pointer arithmetic scales by sizeof(type). a[i] == *(a+i). Pointer size = architecture width. NULL/dangling/wild pointers cause crashes โ always initialize and null after free.
Pointers and Memory Addressing โ Flashcards
Cover the answer, recall, then check. 12 cards on C pointers and memory.
Q1. What does a pointer variable store?
A1. The memory address of another object (not the value itself).
Q2. What does p + 1 advance by for int *p?
A2. sizeof(int) bytes (typically 4), not 1 byte โ pointer arithmetic scales by the pointed-to type.
Q3. What is a[i] equivalent to in pointer form?
A3. *(a + i), which is also *(i + a) and therefore i[a] โ all compile identically.
Q4. Difference between const int *p and int *const p?
A4. const int *p: value is read-only, pointer can move. int *const p: pointer is fixed, value can change.
Q5. Does the size of a pointer depend on what it points to?
A5. No โ it depends on the architecture (8 bytes on 64-bit). sizeof(char*) == sizeof(double*).
Q6. What is a dangling pointer?
A6. A pointer that references memory that has been freed or has gone out of scope; dereferencing it is undefined behavior.
Q7. What is a wild pointer?
A7. An uninitialized pointer holding a garbage address, so it points to an unpredictable location.
Q8. Why set p = NULL after free(p)?
A8. To prevent a dangling pointer and guard against accidental double-free / use-after-free.
Q9. What is a void* and its limitation?
A9. A generic pointer that holds any address, but must be cast before dereferencing; pointer arithmetic on it is not allowed in standard C.
Q10. Why do you need a double pointer (int **) to swap pointers in a function?
A10. C is pass-by-value; to modify the caller's pointer you must pass its address, i.e. a pointer to the pointer.
Q11. What does &x return?
A11. The memory address of x (a pointer to x).
Q12. Most common cause of a segmentation fault with pointers?
A12. Dereferencing a NULL, uninitialized (wild), or dangling pointer.
Pointers and Memory Addressing
Pointers are the reason C is both powerful and feared. They are not "hard" โ they are one idea applied consistently: a pointer is a variable whose value is a memory address.
Core idea: every byte of memory has a numeric address. A pointer stores one such address. The & operator asks "what is the address of this object?" and the * operator asks "what object lives at this address?". They are inverses: *(&x) is x.
How it works
Beginner โ declare, point, dereference
int x = 42;
int *p = &x; // p holds the address of x
printf("%d\n", *p); // 42 โ dereference: read what p points to
*p = 7; // write through the pointer; now x == 7
The type int * means "pointer to int". The pointed-to type matters because it tells the compiler how many bytes to read on a dereference and how far one step of pointer arithmetic moves.
Intermediate โ pointer arithmetic and arrays
Adding 1 to an int * advances by sizeof(int) bytes, not one byte. So p + 3 is the address of the third int after p. This is exactly why a[i] is defined as *(a + i) โ array indexing is pointer arithmetic. A curious consequence: a[i] == *(a+i) == *(i+a) == i[a], so 3[arr] compiles (though never write it in production).
Advanced โ indirection levels and the null/dangling traps
A double **pp is a pointer to a pointer โ needed when a function must modify the caller's pointer (e.g. allocating and returning via an out-parameter). Two lethal states:
- NULL pointer: address 0, "points to nothing"; dereferencing it is undefined (usually a segfault).
- Dangling pointer: points to memory that has been freed or gone out of scope. The pointer still holds the old address, but the object is gone.
void alloc_row(int **out, int n) { // modify caller's pointer via **
*out = malloc(n * sizeof(int));
}
Worked example โ swap by pointer
void swap(int *a, int *b) {
int t = *a; *a = *b; *b = t;
}
int x = 1, y = 2;
swap(&x, &y); // pass ADDRESSES; now x == 2, y == 1
Passing x, y by value would swap only local copies and leave the originals untouched. Passing &x, &y lets swap reach back into the caller's variables. This is how C simulates pass-by-reference.
Interview & exam relevance
"Why does my swap not work?" (answer: passing by value), "what is sizeof(p) vs sizeof(*p)", and pointer-arithmetic output questions are staples. Understanding that sizeof(pointer) is the address width (8 bytes on 64-bit), regardless of the pointed-to type, is a common differentiator.
Tricks & gotchas
sizeof(p)is the pointer's own size (8 on 64-bit);sizeof(*p)is the pointed-to type's size.- Always initialise pointers โ an uninitialised ("wild") pointer holds garbage and dereferencing it is undefined.
- After
free(p), setp = NULLto avoid a dangling dereference or double-free. - Mnemonic: "& gets the address, * gets the value."
Confusing * in a declaration with * as the dereference operator. In int *p; the star is part of the type ("p is a pointer to int"); in *p = 5; it dereferences. They read identically but do opposite jobs โ one names a pointer, the other follows one.
- โ- A pointer stores an address;
&takes an address,*dereferences it. - โ- Pointer arithmetic scales by the pointed-to type's size:
p+1movessizeof(*p)bytes. - โ-
a[i]is literally*(a+i)โ indexing is pointer arithmetic. - โ- NULL = points nowhere; dangling = points to freed/out-of-scope memory. Both are UB to dereference.
- โ- Pass addresses to let a function mutate the caller's variables.
- โA pointer is just an address in a box. Master the
&/*inverse pair, remember arithmetic scales by type size, and defend against NULL, wild and dangling pointers โ that is 90% of pointer bugs.
Pointers and Memory Addressing โ Worked Example
Worked Example
Problem: A function must swap two integers through pointers, and a second function must let the caller see a newly allocated array. Trace why the naive versions fail and write the correct code.
void bad_swap(int a, int b) { int t = a; a = b; b = t; } // fails
void good_swap(int *a, int *b) { int t = *a; *a = *b; *b = t; }// works
void bad_alloc(int *p, int n) { p = malloc(n * sizeof *p); } // fails: caller's p unchanged
void good_alloc(int **p, int n){ *p = malloc(n * sizeof **p); }// works
Solution:
C passes arguments by value. bad_swap receives copies of a and b, swaps the copies, and the caller's originals are untouched. To mutate the caller's variables we must pass their addresses and dereference: good_swap works on *a and *b, the actual storage.
The same logic applies to bad_alloc: p is a local copy of the caller's pointer. Assigning a new malloc result to p only changes the copy; the caller still sees its old (uninitialised) pointer, and the allocation leaks. good_alloc takes int** (address of the caller's pointer) and writes through *p, so the caller's pointer now points at the new block.
Pointer arithmetic is scaled by the pointee size: if int *q points at address 0x1000, then q + 1 is 0x1004 on a 4-byte-int system, because q + 1 means "next int", not "next byte".
Answer: Use good_swap(&x, &y) and good_alloc(&arr, n). To modify a caller's value you pass its address; to modify a caller's pointer you pass a pointer-to-pointer.
- โ- C is pass-by-value: to change a caller variable, pass &var and dereference.
- โ- To let a function reassign the caller's pointer, pass its address (T**).
- โ- Pointer arithmetic p + k moves by k * sizeof(*p) bytes, not k bytes.