3.1. Process Address Space (CS:APP 7.9, 9.7.2)
3.1.1. Segment Layout
Segments
From low to high virtual addresses:
Segment Contents .text,.rodatacode, string literals, constglobals.datainitialized globals and statics .bsszero-initialized and uninitialized globals and statics heap mallocmemory, grows upwardshared libraries libcetc. (mmapregion)stack frames, grows downward kernel inaccessible to user code
Note: Code → .text segment is read only and executable
- is not Writable & executable for security reasons
Example: Where each object lives:

Note: with this layout: stack addresses are bigger than heap addresses always!
- thus
int a = 0(on stack) andint *b = malloc(...)then&a > bbecause of that
int g = 1; // .data
int h; // .bss
const char *m = "hi"; // m in .data, "hi" in .rodata
int main(void) {
static int c = 0; // .bss
int l; // stack or register
int *p = malloc(4); // p on stack/register, *p on heap
}→ Address Space Layout Randomization randomizes the base of the stack, the shared libraries. It is a defense against buffer-overflow attacks.
- makes debugging tougher
.bss section: holds writable static-duration objects whose initial value is all zero bytes.
- thus the binary doesn’t waste space storing all zeros explicitly
int a[1000000]costs nothing to store, only the size!
3.2. Run-Time Stack (CS:APP 3.7.1)
Stack Frame
Each active procedure call owns a frame, created on entry and freed on return. The stack grows toward lower addresses, and
%rsppoints to its top.
callpushes the return address.- The callee’s frame holds saved registers, locals that need memory, and arguments 7 and beyond.
- The first six arguments go in
%rdi, %rsi, %rdx, %rcx, %r8, %r9; the result is returned in%rax.
Note: not all locals are always on the stack → compiler is free to keep locals and arguments only in registers
Example Stack Frame. The fp points to high-address of current frame, sp to low address.

Note: when we pop a frame, we don’t zero it, we just keep track of the stack bottom.
How do we know frame size? (i.e. by how much do we need to decrease / increase stack / frame pointer):
- the frame size is known at compile time
- unknown size objects are put on heap
- thus code gets
addi sp, sp, -32on entry and on exit+32inserted as constants.
Also note, frame pointer is technically not necessary, -O2 omits it.
3.3. Pointers and Memory (CS:APP 3.10.1, 3.8.2, 9.9)
3.3.1. Pointer Basics
Pointer
A pointer is a variable whose value is the address of an object.
NULLis guaranteed never to point to an object
Important: dereferencing NULL pointer segfaults on Linux.
- it’s literally just
0x00000...
Address-of and Dereference Are Inverse
If
x : Tthen&x : T *, and ifp : T *then*p : T. Hence*&xisxand&*pisp.
Inverse operators:
&x(address-of) gives you a pointer tox.- if
xhas typeT, then&xhas typeT*
- if
*p(dereference) gives you the object a pointer points to.- if
phas typeT*, then*phas typeT
- if
Note: *&x ist just x and &*p is just p
Example:
int x = 5;
int *p = &x;
int **pp = &p; // pointer to pointer to int
**pp = 7; // x == 7
printf("%p\n", (void *) p); // %p expects void *Note: we can technically have int *******...****p very long pointer chains, nothing prevents this.
- useful for 3d arrays for example:
a[i] = *(a + i)- thus
a[i][j][k] = *(*(*(a + i) + j) + k)
- thus
- especially useful for non-regular arrays
3.3.2. Pointer Arithmetic
Pointer Arithmetic
For
T *p, the expressionp + npoints bytes further.
p - qcounts elements (typeptrdiff_t).
Note: Can only add integers to a pointer! Nothing else (except if it casts automatically).
Note: Casting a pointer changes the stride.
- Arithmetic on
void *is not standard C (GCC uses stride 1 as an extension). There are no bounds checks.
→ sizeof is evaluated at compile time to size in bytes!
Example: With int *p holding 0x1000:
p + 1→0x1004(char *)p + 1→0x1001(as char is 1 byte)(long *)p + 1→0x1008
For int arr[3] = {1, 2, 3}, the bytes seen through char *c = (char *)arr on little-endian x86 are c[0..3] and c[4] .
- little endian so
1is[1, 0, 0, 0](4 byte integers = 32 bits)
Note: there’s no checks on where a pointer points to → we can just ready anything (segfault possible though).
3.3.3. Dynamic Allocation
malloc, calloc, free
malloc(n)returnsnuninitialized bytes, orNULLon failure.calloc(len, size)returnslen * sizezeroed bytes and fails if that product overflows.free(p)releases a block. Each block must be freed exactly once; forgetting it leaks memory, freeing twice is UB.
We can malloc some memory in the following way: malloc(length * (sizeof(T)));
- this gives us length times the amount of memory needed by type
T
→ we then should check that the pointer returned by this is!= NULL
Note: not guaranteed that malloc’ed memory is actually s, we need to memset:
memset(data, 0, length * (sizeof(T)));
Note: Malloc returns 8 or 16 byte-boundary aligned memory usually.
Example:
T *data = malloc(length * sizeof(T));
if (data == NULL) { /* handle error */ }
memset(data, 0, length * sizeof(T));
// equivalent, and overflow-safe:
T *data2 = calloc(length, sizeof(T));
free(data);
free(data2);3.4. Arrays vs Pointers
3.4.1. Decay Rules
Subscript Is Pointer Arithmetic
A[i]is defined as*(A + i). HenceA[i]i[A].
This is just syntax sugar for us. Note:
- for
int a[10]we haveassert(a == &(a[0]))i.e.&a == a - it really is just the pointer to the first element.
Array Decay
An array expression is converted to a pointer to its first element, except:
- as the operand of
sizeof:sizeof(a)is the size of the whole array- as the operand of
&:&ahas the same address but typeT (*)[N]
Note: Arrays are not assignable: a = b is a compile error.
Example: For int a[10]:
sizeof(a)= 40 = 10 * bytessizeof(&a[0])= 8 → pointer size, since&a[0]isint *a + 1is address&a + 1is address- since
sizeof(a) = 40! and&pgives us pointer to objectint[10].
- since
Note: as a string-literal initializer: char a[] = "..." is a pointer to stack and modifiable.
char *b = "Hello"is pointer to read only section of memory (.data, not modifiable)
Note: 5[a] == a[5] is valid, since addition is commutative!
3.4.2. Parameters and Pass-by-Reference
Array Parameters Are Pointers
As function parameters,
T a[],T a[42]andT *aare identical. Inside the function,sizeof(a). The length must be passed separately.

C passes every argument by value.
- for complex types, that value is the value of the pointer to that object
- pointer to array, pointer to struct
- A function can modify the caller’s objects only through pointers to them.
Example:
void broken_swap(int a, int b) { int t = a; a = b; b = t; } // swaps copies
void swap(int *a, int *b) { int t = *a; *a = *b; *b = t; }
swap(&x, &y);q
Note: We cannot assign array1 = array2 (array1 is not modifiable lvalue, but to be LHS of = we need a modifiable one).
int array1[42]is an object of typeint[42]→ really is an array!- this is cursed, but
int array[]decays to beingint * arraypointer. And= array2always decays to&array[0].

This would work, but it leaves array1 untouched:
int (*p)[42] = &array1; // p points to the whole array1
p = &array2; // fine: p is a modifiable lvalue, types matchTODO
3.5. Function Pointers
Function Pointer
int (*f)(int)declares a pointer to a function that takes anintand returns anint. A function name decays to a pointer, just like an array name does.f(x)and(*f)(x)are equivalent calls.
Reading Declarations
Start at the identifier, read right while possible (
[],()), then left (*, the type), and step outward through parentheses.
Example:
| Declaration | Meaning |
|---|---|
int *p[13] | array of 13 pointers to int |
int (*p)[13] | pointer to an array of 13 int |
int *f(void) | function returning int * |
int (*f)(void) | pointer to a function returning int |
Example: A qsort comparator. The comparison form avoids the overflow that x - y could cause:
int cmp_int(const void *a, const void *b) {
int x = *(const int *)a, y = *(const int *)b;
return (x > y) - (x < y);
}
qsort(arr, n, sizeof(int), cmp_int);Extra
Reading Pointer Expressions
The golden rule is: declaration mirrors use.
This means that int *p reads “the expression *p is an int”.
Box-and-Arrow Diagrams
We can draw the current state of the program’s memory, denoting pointers by arrows pointing to other memory locations.
This helps figure out what is going on.
Example:
