5.1 Allocator
Hierarchy in the slides: pages heap blocks payload.
- the allocator get’s pages (or less) from the OS using
mmapandsbrk(extend heap)- it can only use what’s inside there
- in the heap, it can split that up into blocks
- the block contains the management data (i.e. size) + payload (which is what the program actually gets)
Allocator:
- is not allowed to use
malloccall for defrag → must respond immediately
5.2 Fragmentation
Internal vs. External Fragmentation
Example Internal Fragmentation

Example External fragmentation

5.3 Freeing
How much to free
We keep the length of a block in a head word, just ahead of it (header field).

On free, we then read this.
Keeping track of Free Blocks
To keep track of free blocks, we can:
- implicit list store the length of the free blocks (as a header)
- explicit list use the free space to keep a linked list with a pointer to the next free node
- or other…

5.4 Implicit Free Lists
Properties Summary
- very simple implementation
- allocation linear runtime in the worst-case
- free constant time worst case, even with coalescing
- memory usage
- depends on placement policy
- first-fit, next-fit, best-fit
- not used in practice → linear allocation is too bad
Want to store the length and allocated in one word
- use the last bit as a allocated flag
- mask out the bit with
*p & -2(as -2 has last bit = 0)
- mask out the bit with
- can only get even aligned addresses → is ok

How to find the open space:
- first fit
- search list from beginning, choose first free block that fits
- usually causes “splinters” at start of list
- search list from beginning, choose first free block that fits
- next fit
- start where we last left off → faster since we don’t need to rescan
- fragmentation may be worse
- best fit → scan them all
int *p = start;
while ((p < end) && (
(*p & 1) || // already allocated
(*p <= len) // too small
)) {
p = p + (*p & -2)
}Allocating a new block:

5.4.1 Coalescing in Implicit Free List
Freeing a block should be as simple as “clear the allocated bit”, but this leads to false fragmentation!
- We get multiple chunks which are free, but not in one block
- when we then look for free memory, there might be enough space, but the allocator won’t find it

We need Coalescing → we want to merge two adjacent free blocks.
5.4.2 Bidirectional Coalescing (Knuth)
This is not possible with only a header bit → we need the footer to indicate the size of the block at the end.

Note: this style of header / footer leads to internal fragmentation (i.e. unused space at start and end) → with alignment cost on top
Then we can “doubly linked list” jump around.
coalescing → four possible cases
- this cannot cascade, since we can only free one block per call
- we always coalesce at free → no buildup

5.4.3 Allocator Policies
Do we do:
- immediate coalescing → coalesce each time
freeis called - deferred coalescing → try to improve
freeperformance by deferring until needed- coalesce as you scan via malloc
- coalesce when external frag reaches threshold
We can use segregated free lists (one list per “size” threshhold) to approximate best fit placement without searching entire list.
5.5 Explicit Free Lists
With the implicit list, we noticed that the header/footer tags are oonly needed for non-allocated blocks!
We can thus build an explicit free list, which is a doubly-linked-list of only free blocks.

Since we don’t impose any constraints on the ordering, they will be all over the place in memory.
- not cache efficient?

5.5.1 Allocating
When allocating, once we found a block large enough:
- move the pointers to the “end of the allocated block” → i.e. new start of free section
- relink the linked list + adjust metadata

5.5.2 Freeing
For freeing, we need to decide where in the list to put the newly freed block
- LIFO
- insert at beginning of freed list (simple + constant time)
- empirical: fragmentation worse than address ordered
- Adress Ordered
- insert freed-block so free-list is always in address order
- addr(prev) < addr(curr) < addr(next)
- requires search but fragmentation better.
- insert freed-block so free-list is always in address order
LIFO Cases:
- no coalescing, just splice in new pointers to the block at the start
- free block before/after
- when freeing → always check word before/after to see if there’s free block
- we know this from the header/footer
- splice out the other block
- modify headers for new size and relink at start
- when freeing → always check word before/after to see if there’s free block
- free block before AND after
- splice out both pred and succ blocks → coalesce all three
- insert + relink at the start
5.5.3 Summary
Comparison to implicit:
- allocate is linear to free blocks vs. all blocks (for implicit)
- much faster when memory is full
- slightly more complicated
- extra space needed
most commonly used: explicit + segregated free lists.
5.6 Segregated Free (“seglist”) List
Each size class of blocks has it’s own free list. Keep lists of (1-2, 3, 4, 5-8, 9-inf or similar, ex: one class for each power of two). Then we can find blocks of the necessary size faster.
5.7 Garbage Collection
GC is automatic reclamation of heap-allocated storage → application never has to free.
Example Memory becoming inaccessible
void foo() {
int *p = malloc(128);
return; // from here on out, p is now garbage collectible -> not accessible anymore
}Assumptions necessary for GC:
- must be able to differentiate pointers from non-pointers → no explicit pointer manipulations
- if you can cast int → pointer, you can always access anything
- thus no GC possible
- all pointers point to start of block
→ C violates both of this
GC views memory as a directed graph
- root nodes (always accessible, like modules, statics, globals, etc…)
- heap nodes (accessible from root nodes)
- collect them when they are not connected anymore
- must detect cycles
Mark & Swap:
- DFS from root nodes, mark all reachable
- then dealloc all non-marked
5.8 Memory-related Perils and Pitfalls
- Dereferencing bad pointers
- make sure what you dereference is a pointer
int val,scanf("%d", val)→ scanf requires an address, here it writes into whatevervalwas uninitialised as
- Don’t assume heap data is initialised to zero
- Overwriting memory through mallocing
- if you have 2d-array (i.e.
npointers to int array) and allocsizeof(int)notsizeof(*int)
- if you have 2d-array (i.e.
- Overwriting memory
char s[8]buffer andgets(s)→ if someone gives large input we have buffer overflow- overwrite meaningful data after the buffer.
- Understand pointer arithmetic
- when going in an array / buffer
p += sizeof(int)is wrong → pointer arithmetic already increments by pointer datatype size- this is what we used to find LE vs. BE by casting to
char *
- this is what we used to find LE vs. BE by casting to
- Referencing non-existent variables
- local variables = stack → disappear when function returns
int * foo() { int val; return &val; }→ &val will not be what we think it is on return- stackframe got yoinked
- Double freeing OR referencing freed blocks OR never freeing
- Classic memory leak
foo() { int *x = malloc(...); return; }→ can never free x again, since we lost access to it as it was on stack
- Don’t free start of DSA → if we free head pointer of Linked-List, can never free the rest
- need to loop over
- Classic memory leak
- Order of operations
*size--is*(size--)!