malloc
) to acquire virtual memory (VM) at run time.
Allocator maintains the heap as a collection of variable sized blocks, which are either allocated or free.
malloc
and free
in C)new
and garbage collection in Java)This lecture: explicit memory allocation
malloc
Packagevoid *malloc(size_t size)
size
bytes aligned to a 16-byte boundary (on x86-64); if size == 0
, returns NULL
NULL
and sets errno
to ENOMEM
void free(void *p)
p
to pool of available memoryp
must come from a previous call to malloc
, calloc
, or realloc
calloc
: version of malloc
that initializes allocated block to zerorealloc
: changes the size of a previously allocated blocksbrk
: used internally by allocators to grow or shrink the heapmalloc
Example#include <stdio.h>
#include <stdlib.h>
void foo(long n) {
long i, *p;
/* Allocate a block of n longs */
p = (long *) malloc(n * sizeof(long));
if (p == NULL) {
perror("malloc");
exit(0);
}
/* Initialize allocated block */
for (i=0; i<n; i++)
p[i] = i;
/* Do something with p */
...
/* Return allocated block to the heap */
free(p);
}
mm.c
mm_malloc
and mm_free
malloc
are double-word alignedmalloc
and free
requestsfree
request must be to a malloc
’d blockmalloc
requestsmalloc
’dGiven some sequence of malloc
and free
requests:
\[R_{0}, R_{1}, \ldots, R_{k}, \ldots, R_{n-1}\]
malloc
calls and 5,000 free
calls in 10 secondsGiven some sequence of malloc
and free
requests:
\[R_{0}, R_{1}, \ldots, R_{k}, \ldots, R_{n-1}\]
malloc(p)
results in a block with a payload of p
bytessbrk
malloc
Heap Visualization ExampleFragmentation causes poor memory utilization
Internal fragmentation: For a given block, internal framentation occurs if payload is smaller than block size
External fragmentation: occurs when there is enough aggregate heap memory, but no single free block is large enough
How do we know how much memory to free given only a pointer?
How do we keep track of the free blocks?
What do we do with the extra space when allocating a structure that is smaller than the free block it is place?
How do we pick a block to use for allocation – many might fit?
How do we reuse a block that has been freed?
Keep the length (in bytes) of a block in the word preceding the block, including the header
Requires an extra word for every allocated block
Block declaration
typedef unint64_t word_t;
typedef struct block {
word_t header;
unsigned char payload[0]; // zero length array
} block_t;
Getting payload from block pointer
return (void *) (block->payload);
Getting header from payload
return (void *) ((unsigned char *) bp - offsetof(block_t, payload));
Getting allocated bit from header
return header & 0x1;
Getting size from header
return header & ~0xfL;
Initializing header
block->header = size | alloc;
Find next block
static block_t *find_next(block_t *block) {
return (block_t *) ((unsigned char *) block
+ get_size(block));
}
static block_t *find_fit(size_t asize) {
block_t *block;
for (block = heap_start; block != heap_end;
block = find_next(block))
{
if (!(get_alloc(block)) && (asize <= get_size(block)))
return block;
}
return NULL; // No fit found
}
// Warning: This code is incomplete
static void split_block(block_t *block, size_t asize) {
size_t block_size = get_size(block);
if ((block_size - asize) >= min_block_size) {
write_header(block, asize, true);
block_t *block_next = find_next(block);
write_header(block_next, block_size - asize, false);
}
}
Join (coalesce) with next/previous blocks, if they are free
Locating footer of current block
const size_t dsize = 2 * sizeof(word_t);
static word_t *header_to_footer(block_t *block) {
size_t asize = get_size(block);
return (word_t *) (block->payload + asize - dsize);
}
Locating footer of previous block
static word_t *find_prev_footer(block_t *block) {
return &(block->header) - 1);
}
static void split_block(block_t *block, size_t asize) {
size_t block_size = get_size(block);
if ((block_size - asize) >= min_block_size) {
write_header(block, asize, true);
write_footer(block, asize, true);
block_t *block_next = find_next(block);
write_header(block_next, block_size - asize, false);
write_footer(block_next, block_size - asize, false);
}
}
const size_t dsize = 2*sizeof(word_t);
void *mm_malloc(size_t size)
{
size_t asize = round_up(size + dsize, dsize);
block_t *block = find_fit(asize);
if (block == NULL)
return NULL;
size_t block_size = get_size(block);
write_header(block, block_size, true);
write_footer(block, block_size, true);
split_block(block, asize);
return header_to_payload(block);
}
void mm_free(void *bp)
{
block_t *block = payload_to_header(bp);
size_t size = get_size(block);
write_header(block, size, false);
write_footer(block, size, false);
coalesce_block(block);
}
Internal fragmentation
Boundary tag needed only for free blocks
When sizes are multiples of 16, have 4 spare bits
Header: Use 2 bits (address bits always zero due to alignment):
(prev_block) << 1 | (curr_block)
free
is calledfree
by deferring coalescing until neededImplementation: very simple
Allocate cost: linear time worst case
Free cost: constant time worst case (even with coalescing)
Memory overhead: depends on placement policy
Not used in practice for malloc
/free
because of linear time allocation
The concepts of splitting and boundary tag coalescing are general to all allocators