Data representation 4: Dynamic allocation, invariants

Overview

Today, we’ll use the problem set to explore alignment, dynamic allocation, invariants, tests, and internal metadata.

Problem set comment

After memory reuse, your m61_malloc might use a helper function like this:

static void* m61_find_free_space(size_t sz) {
    // do we have a freed allocation that will work?
    for (allocation& a : freed allocation set) {
        if (a is at least sz bytes big) {
            void* ptr = first byte in a;
            remove a from freed allocation set;
            return ptr;
        }
    }
    // otherwise fail
    return nullptr;
}

Specifications

/// m61_malloc(sz, file, line)
///    Returns a pointer to `sz` bytes of freshly-allocated dynamic memory.
///    The memory is not initialized. If `sz == 0`, then m61_malloc may
///    return either `nullptr` or a pointer to a unique allocation.
///    The allocation request was made at source code location `file`:`line`.

m61_find_free_space specification

/// m61_find_free_space(sz)
///    Allocates a block of `sz` bytes and returns a pointer to that block.
///    Returns `nullptr` on failure. The newly-allocated block does not
///    overlap with any other concurrently-allocated block.

Handout code

void* m61_malloc(size_t sz, const char* file, int line) {
    (void) file, (void) line;   // avoid uninitialized variable warnings
    // Your code here.
    if (default_buffer.pos + sz > default_buffer.size) {
        // Not enough space left in default buffer for allocation
        return nullptr;
    }

    // Otherwise there is enough space; claim the next `sz` bytes
    void* ptr = &default_buffer.buffer[default_buffer.pos];
    default_buffer.pos += sz;
    return ptr;
}

Rewritten m61_malloc

void* m61_find_free_space(size_t sz) {
    if (default_buffer.pos + sz <= default_buffer.sz) {
        void* ptr = &default_buffer.buffer[default_buffer.pos];
        default_buffer.pos += sz;
        return ptr;
    }
    return nullptr;
}

void* m61_malloc(size_t sz, const char* file, int line) {
    void* ptr = m61_find_free_space(sz);
    return ptr;
}

Adding statistics

void* m61_find_free_space(size_t sz) {
    if (default_buffer.pos + sz <= default_buffer.sz) {
        void* ptr = &default_buffer.buffer[default_buffer.pos];
        default_buffer.pos += sz;
        return ptr;
    }
    return nullptr;
}

void* m61_malloc(size_t sz, const char* file, int line) {
    void* ptr = m61_find_free_space(sz);
    if (ptr) {
        global_statistics.ntotal += 1;
        global_statistics.total_size += sz;
        global_statistics.nactive += 1;
        global_statistics.active_size += sz;
    } else {
        global_statistics.nfail += 1;
        global_statistics.fail_size += sz;
    }
    return ptr;
}

Alignment sidebar

Sizes of primitive types on x86-64

Type

Size

char

1

short

2

int

4

long

8

long long

8

float

4

double

8

long double

16

T*

8

Standard sizes of primitive types

Type

x86-64 Linux size

Standard size

char

1

1

short

2

≥1

int

4

sizeof(short)

long

8

sizeof(int)

long long

8

sizeof(long)

float

4

≥4 (probably)

double

8

sizeof(float), ≥8 (probably)

long double

16

sizeof(double)

T*

8

N/A

Alignment

Alignments of primitive types

Type

Alignment

char

1

short

2

int

4

long

8

long long

8

float

4

double

8

long double

16

T*

8

std::max_align_t

16

Inductive alignment

Adding alignment

void* m61_find_free_space(size_t sz) {
    if (default_buffer.pos + sz <= default_buffer.sz) {
        void* ptr = &default_buffer.buffer[default_buffer.pos];
        default_buffer.pos += sz;
        return ptr;
    }
    return nullptr;
}

void* m61_malloc(size_t sz, const char* file, int line) {
    size_t alignment_space = /* YOUR CODE HERE */;
    size_t aligned_sz = sz + alignment_space;
    void* ptr = m61_find_free_space(aligned_sz);
    if (ptr) {
        global_statistics.ntotal += 1;
        global_statistics.total_size += sz;
        global_statistics.nactive += 1;
        global_statistics.active_size += sz;
    } else {
        global_statistics.nfail += 1;
        global_statistics.fail_size += sz;
    }
    return ptr;
}

Checking your arithmetic

Reusing memory

void* m61_find_free_space(size_t sz) {
    if (default_buffer.pos + sz <= default_buffer.sz) {
        // can perform allocation using default buffer
        void* ptr = &default_buffer.buffer[default_buffer.pos];
        default_buffer.pos += sz;
        return ptr;
    }

    // otherwise, try previously-freed allocations
    for (allocation& a : freed allocation set) {
        if (a is at least sz bytes big) {
            void* ptr = first byte in a;
            remove a from freed allocation set;
            return ptr;
        }
    }

    return nullptr;
}

Questions about allocation reuse