Simple Malloc Implementation












1














I've written an implementation of malloc, realloc, and free. I wanted to make a malloc implementation that is sufficiently simple and easy to maintain.



I don't care so much for performance, but I'd like to know if anything stands out that can easily be fixed.



An explanation of how it works is in the source file. Other malloc reviews I've seen on this site use linked lists, but just note that this implementation does not.



The prefix alloy is the name of the project, so if you see it in the global functions, that's what it means and you can ignore it.



Here's malloc.h



#ifndef alloy_malloc_h
#define alloy_malloc_h

#ifdef __cplusplus
extern "C" {
#endif

#ifndef NULL

/** This value represents an invalid memory address.
* It contains a value of zero, by default. Zero is
* usually the standard value of a 'null' pointer. */
#define NULL ((void *) 0x00)

#endif

/** This function must be called for the memory
* allocation functions to work. It tells malloc
* where it can find extra memory at.
* @param addr The base address to allocate memory at.
* @param size The max number of bytes that can be allocated.
* This must be at least 64 bytes on 64-bit systems and
* 32 bytes on 32-bit systems.
* @returns Zero on success, a negative one on failure. */
int alloy_init_heap(void *addr, unsigned long int size);

/** This function allocates a new memory section of a specified size.
* @param size The number of bytes to allocate.
* @returns A pointer to the memory section, if one is
* found that can fit the number of bytes requested.
* If the memory can't be allocated, then a @ref NULL
* value is returned instead. */
void *alloy_malloc(unsigned long int size);

/** Resizes an existing memory section that was allocated with @ref alloy_malloc.
* New memory can also be allocated with this function, by passing @p addr with
* a value of @ref NULL. The data from the previous section is copied to the new section.
* @param addr The address of the memory section to resize.
* @param size The number of bytes to resize the section to.
* @returns A pointer to the newly allocation memory section.
* If space could not be found for the new section, then a value
* of @ref NULL is returned instead and the memory section remains unchanged. */
void *alloy_realloc(void *addr, unsigned long int size);

/** Releases memory previously allocated with @ref alloy_malloc
* or @ref alloy_realloc. If the memory address passed to this
* function does not exist within the heap, then this function has no effect.
* @param addr The address of the memory section to free. */
void alloy_free(void *addr);

#ifdef __cplusplus
} /* extern "C" */
#endif

#endif // alloy_malloc_h


Here's malloc.c



#include "malloc.h"

/* This file contains the malloc implementation for the Alloy project.
*
* Here's how it works.
*
* The implementation is made up of a 'header' and an allocation table.
*
* The header is used for tracking the position of the allocation table
* entries, then number of allocations, and the size of the heap. The header
* sits at the very beginning of the heap and remains there for the duration
* of the library usage.
*
* The allocation table is used for keeping track of the memory sections
* to ensure that they don't overlap. When the heap is first initialized,
* the allocation table begins immediately after the header. As more allocations
* are added to the table, it gets resized and moved around the heap. The
* first two allocations in the allocation table that are made is the heap
* header allocation and the allocation entry for the allocation table itself.
* Thus, the allocation table is able to resize itself.
* */

/** Used for memory sizes. */
typedef unsigned long int size_type;

/** The base address of the heap. */
unsigned char *heap_addr = NULL;

/** A single memory allocation. */
struct allocation {
/// The memory address that the
/// allocation is responsible for.
void *addr;
/// The number of bytes allocated
/// for the address.
size_type size;
};

/** Gives the allocation the highest address value
* and a size of zero. */
static void mark_allocation_invalid(struct allocation *allocation) {
allocation->addr = (void *) 0xffffffffffffffff;
allocation->size = 0;
}

/** Indicates whether or not an allocation is invalid. */
static unsigned int is_invalid_allocation(const struct allocation *allocation) {
return allocation->addr == ((void *) 0xffffffffffffffff);
}

/** Magic number used for sanity checking (= speed of light in m/s). */
const size_type valid_magic_number = 299792458;

/** Used at the start of the heap
* to keep track of the allocation table. */
struct header {
/** Used for verifying that the
* heap was initialized properly. */
size_type magic_number;
/** The array of allocations. */
struct allocation *allocations;
/** The number of allocations made. */
size_type allocation_count;
/** The size of the heap (including this header.) */
size_type heap_size;
};

/** Retrieves the header from the heap address.
* This function also checks to ensure that the
* heap has been initialized first.
* @returns A pointer to the header. */
static struct header *get_header() {

if (heap_addr == NULL) {
return NULL;
}

struct header *header = (struct header *) heap_addr;

if (header->magic_number != valid_magic_number) {
return NULL;
}

return header;
}

/** Copies memory from one location to another. */
static void alloy_memcpy(void *dst, const void *src, size_type size) {

unsigned char *d = (unsigned char *) dst;

const unsigned char *s = (const unsigned char *) src;

for (size_type i = 0; i < size; i++) {
d[i] = s[i];
}
}

/** Performs a single iteration of the bubblesort algorithm,
* where the sort key is the allocation address. The number of
* sorted items is returned. */
static size_type single_bubblesort(struct allocation *allocations,
size_type allocation_count) {

struct allocation *a;
struct allocation *b;
struct allocation tmp;

size_type sorted = 0;

for (size_type i = 0; (i + 1) < allocation_count; i++) {
a = &allocations[i];
b = &allocations[i + 1];
size_type a_addr = (size_type) a->addr;
size_type b_addr = (size_type) b->addr;
if (a_addr > b_addr) {
tmp.addr = a->addr;
tmp.size = a->size;
a->addr = b->addr;
a->size = b->size;
b->addr = tmp.addr;
b->size = tmp.size;
sorted = 1;
}
}

return sorted;
}

/** Sorts the allocations to have ascending
* starting addresses. Should be called whenever
* the allocation table is modified. */
static void sort_allocations() {

struct header *header = get_header();
if (header == NULL) {
return;
}

size_type sorted = 0;

for (;;) {
sorted = single_bubblesort(header->allocations, header->allocation_count);
if (!sorted) {
break;
}
}
}

/** Locates an address that will fit the specified size.
* This function does not reserve the address that is found,
* it just locates an address that will fit the space requested. */
static void *find_space_for(size_type size) {

struct header *header = get_header();
if (header == NULL) {
return NULL;
}

size_type next_addr = (size_type) heap_addr;

for (size_type i = 0; i < header->allocation_count; i++) {

struct allocation *allocation = &header->allocations[i];

size_type allocation_addr = (size_type) allocation->addr;

if ((next_addr + size) <= allocation_addr) {
break;
} else {
next_addr = allocation_addr + allocation->size;
}
}

if ((next_addr + size) <= (((size_type) heap_addr) + header->heap_size)) {
return (void *) next_addr;
}

return NULL;
}

/** Locates an allocation using iterative binary search algorithm,
* where the search key is the allocation address. */
static struct allocation *binary_search(struct allocation *allocations,
size_type left_index,
size_type right_index,
size_type addr) {

while (left_index <= right_index) {

size_type middle_index = left_index + ((right_index - left_index) / 2);

size_type candidate_addr = (size_type) allocations[middle_index].addr;

if (candidate_addr == addr) {
return &allocations[middle_index];
} else if (candidate_addr < addr) {
left_index = middle_index + 1;
} else {
right_index = middle_index - 1;
}
}

return NULL;
}

/** Locates the allocation entry for a specified address.
* If no allocation exists for the specified address,
* then a null pointer is returned. */
static struct allocation *find_allocation(void *addr) {

/* Check if addr is NULL so we can just quickly exit. */
if (addr == NULL) {
return NULL;
}

struct header *header = get_header();

if (header == NULL) {
return NULL;
}

return binary_search(header->allocations, 0, header->allocation_count, (size_type) addr);
}

/** Creates an allocation entry within the allocation table. */
static struct allocation *create_allocation(void) {

struct header *header = get_header();

if (header == NULL) {
return NULL;
}

size_type next_allocations_size = sizeof(struct allocation) * (header->allocation_count + 1);

struct allocation *next_allocations = find_space_for(next_allocations_size);
if (next_allocations == NULL) {
return NULL;
}

for (size_type i = 0; i < header->allocation_count; i++) {
struct allocation *allocation = &header->allocations[i];
if (allocation->addr == header->allocations) {
allocation->addr = next_allocations;
allocation->size = next_allocations_size;
sort_allocations();
break;
}
}

alloy_memcpy(next_allocations, header->allocations, sizeof(struct allocation) * header->allocation_count);

header->allocations = next_allocations;
header->allocation_count++;

struct allocation *new_allocation = &next_allocations[header->allocation_count - 1];
mark_allocation_invalid(new_allocation);
return new_allocation;
}

/** Removes an invalid allocation from the end of the
* allocation table, if one is present. */
static void remove_invalid_allocation_if_present(void) {

struct header *header = get_header();
if (header == NULL) {
return;
}

if (header->allocation_count <= 0) {
return;
}

struct allocation *last_allocation = &header->allocations[header->allocation_count - 1];
if (is_invalid_allocation(last_allocation)) {
header->allocation_count--;
}
}

int alloy_init_heap(void *addr, unsigned long int size) {

/** Ensure that the heap given can fit a header and two allocations. */
if (size < (sizeof(struct header) + (sizeof(struct allocation) * 2))) {
return -1;
}

heap_addr = (unsigned char *) addr;

/** This creates an allocation table for the heap header.
* This ensures that memory allocations won't allocate space
* over the header. */
struct allocation *first_allocation = (struct allocation *) &heap_addr[sizeof(struct header)];
first_allocation->addr = heap_addr;
first_allocation->size = sizeof(struct header);

/** This creates an allocation entry for the allocation table itself.
* This ensures that memory isn't allocated over the allocation table
* and it also allows the allocation table to be resized. */
struct allocation *second_allocation = first_allocation + 1;
second_allocation->addr = first_allocation;
second_allocation->size = sizeof(struct allocation) * 2;

/** Initialize the header. */
struct header *header = (struct header *) heap_addr;
header->magic_number = valid_magic_number;
header->allocations = first_allocation;
header->allocation_count = 2;
header->heap_size = size;

return 0;
}

void *alloy_malloc(unsigned long int size) {
return alloy_realloc(NULL, size);
}

void *alloy_realloc(void *addr, unsigned long int size) {

struct header *header = get_header();
if (header == NULL) {
return NULL;
}

struct allocation *dst_entry = NULL;

struct allocation *existing_entry = find_allocation(addr);
if (existing_entry != NULL) {
dst_entry = existing_entry;
} else {
dst_entry = create_allocation();
if (dst_entry == NULL) {
return NULL;
}
}

/* align size to 16 bytes. */
size = ((size + 15) / 16) * 16;

void *next_addr = find_space_for(size);
if (next_addr == NULL) {
remove_invalid_allocation_if_present();
return NULL;
}

if (existing_entry != NULL) {
alloy_memcpy(next_addr, existing_entry->addr, existing_entry->size);
}

dst_entry->addr = next_addr;
dst_entry->size = size;

sort_allocations();

return next_addr;
}

void alloy_free(void *addr) {

struct header *header = get_header();
if (header == NULL) {
return;
}

struct allocation *allocation = find_allocation(addr);
if (allocation != NULL) {
mark_allocation_invalid(allocation);
/** Invalid allocations go to the back
* of the allocation table when sorted. */
sort_allocations();
/** Now that the free'd allocation is at
* the back of the table, just reduce the
* allocation count to get rid of it. */
header->allocation_count--;
}
}


Here's a test I wrote to verify the functions.



#include "malloc.h"

#include <assert.h>
#include <string.h>

int main(void) {

/* Initialize the heap. */

unsigned char heap[1024];

int err = alloy_init_heap(heap, sizeof(heap));
assert(err == 0);

/* Allocate memory that is definitely out of range of
* the heap size given. Ensure that it fails. */

unsigned char *failed_malloc = (unsigned char *) alloy_malloc(2048);
assert(failed_malloc == NULL);

/* Allocate three ranges, A, B, and C and allocate
* them on the heap. */

unsigned char a_expected[32];
memset(a_expected, 'a', sizeof(a_expected));

unsigned char b_expected[64];
memset(b_expected, 'b', sizeof(b_expected));

unsigned char b_expected_2[128];
memset(b_expected_2, 'B', sizeof(b_expected_2));

unsigned char c_expected[128];
memset(c_expected, 'c', sizeof(c_expected));

unsigned char *a = (unsigned char *) alloy_malloc(32);
assert(a != NULL);
memset(a, 'a', 32);

unsigned char *b = (unsigned char *) alloy_malloc(64);
assert(b != NULL);
memset(b, 'b', 64);

unsigned char *c = (unsigned char *) alloy_malloc(128);
assert(c != NULL);
memset(c, 'c', 128);

/* Check to ensure that all memory ranges contain the
* values that were given to them. Make sure they aren't
* overlapping. */

assert(memcmp(a, a_expected, sizeof(a_expected)) == 0);
assert(memcmp(b, b_expected, sizeof(b_expected)) == 0);
assert(memcmp(c, c_expected, sizeof(c_expected)) == 0);

/* The remaining space is 832 (1024 - (32 + 64 + 128)),
* but is should fail due to the memory used for housekeeping. */

failed_malloc = (unsigned char *) alloy_malloc(832);
assert(failed_malloc == NULL);

/* Resize section B, ensure that all sections still contain
* their values. */

b = alloy_realloc(b, 128);
assert(b != NULL);

assert(memcmp(a, a_expected, sizeof(a_expected)) == 0);
assert(memcmp(b, b_expected, sizeof(b_expected)) == 0);
assert(memcmp(c, c_expected, sizeof(c_expected)) == 0);

/* Use the rest of the space in B, ensure that all sections
* still contain their values. */

memset(b, 'B', 128);

assert(memcmp(a, a_expected, sizeof(a_expected)) == 0);
assert(memcmp(b, b_expected_2, sizeof(b_expected_2)) == 0);
assert(memcmp(c, c_expected, sizeof(c_expected)) == 0);

/* Release all the memory and verify that a sufficiently
* large memory section can be allocated again (that would
* otherwise fail.) */

/* 896 is 1024 - 128. */
void *large_section = alloy_malloc(896);
assert(large_section == NULL);

alloy_free(a);
alloy_free(b);
alloy_free(c);

large_section = alloy_malloc(896);
assert(large_section != NULL);

return EXIT_SUCCESS;
}


Here's a test I wrote to benchmark the function (takes 40 seconds on my VM.)



#include "malloc.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(void) {

/* Initialize the heap (32 MiB). */

unsigned long int heap_size = 32UL * 1024UL * 1024UL;

void *heap = malloc(heap_size);
if (heap == NULL) {
fprintf(stderr, "Failed to allocate heap space.n");
return EXIT_FAILURE;
}

int err = alloy_init_heap(heap, heap_size);
if (err != 0) {
free(heap);
fprintf(stderr, "Failed to initialize heap.n");
return EXIT_FAILURE;
}

unsigned long int leftover_size = heap_size;

unsigned long int size_index = 0;

unsigned long int size_array[8] = {
8, 16, 32, 64,
128, 256, 512, 1024
};

while (leftover_size > 0) {

unsigned long int size = size_array[size_index % 8];

void *addr = alloy_malloc(size);
if (addr == NULL) {
break;
}

leftover_size -= size;

size_index++;
}

unsigned long int bytes_allocated = heap_size - leftover_size;

printf("Made %lu allocations.n", size_index);
printf("Allocated %lu bytes.n", bytes_allocated);
printf("Used %lu bytes for house keeping.n", leftover_size);

float space_efficiency = 0.0f;
space_efficiency += bytes_allocated;
space_efficiency /= heap_size;
space_efficiency *= 100.0f;

printf("Heap space efficiency: %f%%n", space_efficiency);

free(heap);

return EXIT_SUCCESS;
}









share|improve this question



























    1














    I've written an implementation of malloc, realloc, and free. I wanted to make a malloc implementation that is sufficiently simple and easy to maintain.



    I don't care so much for performance, but I'd like to know if anything stands out that can easily be fixed.



    An explanation of how it works is in the source file. Other malloc reviews I've seen on this site use linked lists, but just note that this implementation does not.



    The prefix alloy is the name of the project, so if you see it in the global functions, that's what it means and you can ignore it.



    Here's malloc.h



    #ifndef alloy_malloc_h
    #define alloy_malloc_h

    #ifdef __cplusplus
    extern "C" {
    #endif

    #ifndef NULL

    /** This value represents an invalid memory address.
    * It contains a value of zero, by default. Zero is
    * usually the standard value of a 'null' pointer. */
    #define NULL ((void *) 0x00)

    #endif

    /** This function must be called for the memory
    * allocation functions to work. It tells malloc
    * where it can find extra memory at.
    * @param addr The base address to allocate memory at.
    * @param size The max number of bytes that can be allocated.
    * This must be at least 64 bytes on 64-bit systems and
    * 32 bytes on 32-bit systems.
    * @returns Zero on success, a negative one on failure. */
    int alloy_init_heap(void *addr, unsigned long int size);

    /** This function allocates a new memory section of a specified size.
    * @param size The number of bytes to allocate.
    * @returns A pointer to the memory section, if one is
    * found that can fit the number of bytes requested.
    * If the memory can't be allocated, then a @ref NULL
    * value is returned instead. */
    void *alloy_malloc(unsigned long int size);

    /** Resizes an existing memory section that was allocated with @ref alloy_malloc.
    * New memory can also be allocated with this function, by passing @p addr with
    * a value of @ref NULL. The data from the previous section is copied to the new section.
    * @param addr The address of the memory section to resize.
    * @param size The number of bytes to resize the section to.
    * @returns A pointer to the newly allocation memory section.
    * If space could not be found for the new section, then a value
    * of @ref NULL is returned instead and the memory section remains unchanged. */
    void *alloy_realloc(void *addr, unsigned long int size);

    /** Releases memory previously allocated with @ref alloy_malloc
    * or @ref alloy_realloc. If the memory address passed to this
    * function does not exist within the heap, then this function has no effect.
    * @param addr The address of the memory section to free. */
    void alloy_free(void *addr);

    #ifdef __cplusplus
    } /* extern "C" */
    #endif

    #endif // alloy_malloc_h


    Here's malloc.c



    #include "malloc.h"

    /* This file contains the malloc implementation for the Alloy project.
    *
    * Here's how it works.
    *
    * The implementation is made up of a 'header' and an allocation table.
    *
    * The header is used for tracking the position of the allocation table
    * entries, then number of allocations, and the size of the heap. The header
    * sits at the very beginning of the heap and remains there for the duration
    * of the library usage.
    *
    * The allocation table is used for keeping track of the memory sections
    * to ensure that they don't overlap. When the heap is first initialized,
    * the allocation table begins immediately after the header. As more allocations
    * are added to the table, it gets resized and moved around the heap. The
    * first two allocations in the allocation table that are made is the heap
    * header allocation and the allocation entry for the allocation table itself.
    * Thus, the allocation table is able to resize itself.
    * */

    /** Used for memory sizes. */
    typedef unsigned long int size_type;

    /** The base address of the heap. */
    unsigned char *heap_addr = NULL;

    /** A single memory allocation. */
    struct allocation {
    /// The memory address that the
    /// allocation is responsible for.
    void *addr;
    /// The number of bytes allocated
    /// for the address.
    size_type size;
    };

    /** Gives the allocation the highest address value
    * and a size of zero. */
    static void mark_allocation_invalid(struct allocation *allocation) {
    allocation->addr = (void *) 0xffffffffffffffff;
    allocation->size = 0;
    }

    /** Indicates whether or not an allocation is invalid. */
    static unsigned int is_invalid_allocation(const struct allocation *allocation) {
    return allocation->addr == ((void *) 0xffffffffffffffff);
    }

    /** Magic number used for sanity checking (= speed of light in m/s). */
    const size_type valid_magic_number = 299792458;

    /** Used at the start of the heap
    * to keep track of the allocation table. */
    struct header {
    /** Used for verifying that the
    * heap was initialized properly. */
    size_type magic_number;
    /** The array of allocations. */
    struct allocation *allocations;
    /** The number of allocations made. */
    size_type allocation_count;
    /** The size of the heap (including this header.) */
    size_type heap_size;
    };

    /** Retrieves the header from the heap address.
    * This function also checks to ensure that the
    * heap has been initialized first.
    * @returns A pointer to the header. */
    static struct header *get_header() {

    if (heap_addr == NULL) {
    return NULL;
    }

    struct header *header = (struct header *) heap_addr;

    if (header->magic_number != valid_magic_number) {
    return NULL;
    }

    return header;
    }

    /** Copies memory from one location to another. */
    static void alloy_memcpy(void *dst, const void *src, size_type size) {

    unsigned char *d = (unsigned char *) dst;

    const unsigned char *s = (const unsigned char *) src;

    for (size_type i = 0; i < size; i++) {
    d[i] = s[i];
    }
    }

    /** Performs a single iteration of the bubblesort algorithm,
    * where the sort key is the allocation address. The number of
    * sorted items is returned. */
    static size_type single_bubblesort(struct allocation *allocations,
    size_type allocation_count) {

    struct allocation *a;
    struct allocation *b;
    struct allocation tmp;

    size_type sorted = 0;

    for (size_type i = 0; (i + 1) < allocation_count; i++) {
    a = &allocations[i];
    b = &allocations[i + 1];
    size_type a_addr = (size_type) a->addr;
    size_type b_addr = (size_type) b->addr;
    if (a_addr > b_addr) {
    tmp.addr = a->addr;
    tmp.size = a->size;
    a->addr = b->addr;
    a->size = b->size;
    b->addr = tmp.addr;
    b->size = tmp.size;
    sorted = 1;
    }
    }

    return sorted;
    }

    /** Sorts the allocations to have ascending
    * starting addresses. Should be called whenever
    * the allocation table is modified. */
    static void sort_allocations() {

    struct header *header = get_header();
    if (header == NULL) {
    return;
    }

    size_type sorted = 0;

    for (;;) {
    sorted = single_bubblesort(header->allocations, header->allocation_count);
    if (!sorted) {
    break;
    }
    }
    }

    /** Locates an address that will fit the specified size.
    * This function does not reserve the address that is found,
    * it just locates an address that will fit the space requested. */
    static void *find_space_for(size_type size) {

    struct header *header = get_header();
    if (header == NULL) {
    return NULL;
    }

    size_type next_addr = (size_type) heap_addr;

    for (size_type i = 0; i < header->allocation_count; i++) {

    struct allocation *allocation = &header->allocations[i];

    size_type allocation_addr = (size_type) allocation->addr;

    if ((next_addr + size) <= allocation_addr) {
    break;
    } else {
    next_addr = allocation_addr + allocation->size;
    }
    }

    if ((next_addr + size) <= (((size_type) heap_addr) + header->heap_size)) {
    return (void *) next_addr;
    }

    return NULL;
    }

    /** Locates an allocation using iterative binary search algorithm,
    * where the search key is the allocation address. */
    static struct allocation *binary_search(struct allocation *allocations,
    size_type left_index,
    size_type right_index,
    size_type addr) {

    while (left_index <= right_index) {

    size_type middle_index = left_index + ((right_index - left_index) / 2);

    size_type candidate_addr = (size_type) allocations[middle_index].addr;

    if (candidate_addr == addr) {
    return &allocations[middle_index];
    } else if (candidate_addr < addr) {
    left_index = middle_index + 1;
    } else {
    right_index = middle_index - 1;
    }
    }

    return NULL;
    }

    /** Locates the allocation entry for a specified address.
    * If no allocation exists for the specified address,
    * then a null pointer is returned. */
    static struct allocation *find_allocation(void *addr) {

    /* Check if addr is NULL so we can just quickly exit. */
    if (addr == NULL) {
    return NULL;
    }

    struct header *header = get_header();

    if (header == NULL) {
    return NULL;
    }

    return binary_search(header->allocations, 0, header->allocation_count, (size_type) addr);
    }

    /** Creates an allocation entry within the allocation table. */
    static struct allocation *create_allocation(void) {

    struct header *header = get_header();

    if (header == NULL) {
    return NULL;
    }

    size_type next_allocations_size = sizeof(struct allocation) * (header->allocation_count + 1);

    struct allocation *next_allocations = find_space_for(next_allocations_size);
    if (next_allocations == NULL) {
    return NULL;
    }

    for (size_type i = 0; i < header->allocation_count; i++) {
    struct allocation *allocation = &header->allocations[i];
    if (allocation->addr == header->allocations) {
    allocation->addr = next_allocations;
    allocation->size = next_allocations_size;
    sort_allocations();
    break;
    }
    }

    alloy_memcpy(next_allocations, header->allocations, sizeof(struct allocation) * header->allocation_count);

    header->allocations = next_allocations;
    header->allocation_count++;

    struct allocation *new_allocation = &next_allocations[header->allocation_count - 1];
    mark_allocation_invalid(new_allocation);
    return new_allocation;
    }

    /** Removes an invalid allocation from the end of the
    * allocation table, if one is present. */
    static void remove_invalid_allocation_if_present(void) {

    struct header *header = get_header();
    if (header == NULL) {
    return;
    }

    if (header->allocation_count <= 0) {
    return;
    }

    struct allocation *last_allocation = &header->allocations[header->allocation_count - 1];
    if (is_invalid_allocation(last_allocation)) {
    header->allocation_count--;
    }
    }

    int alloy_init_heap(void *addr, unsigned long int size) {

    /** Ensure that the heap given can fit a header and two allocations. */
    if (size < (sizeof(struct header) + (sizeof(struct allocation) * 2))) {
    return -1;
    }

    heap_addr = (unsigned char *) addr;

    /** This creates an allocation table for the heap header.
    * This ensures that memory allocations won't allocate space
    * over the header. */
    struct allocation *first_allocation = (struct allocation *) &heap_addr[sizeof(struct header)];
    first_allocation->addr = heap_addr;
    first_allocation->size = sizeof(struct header);

    /** This creates an allocation entry for the allocation table itself.
    * This ensures that memory isn't allocated over the allocation table
    * and it also allows the allocation table to be resized. */
    struct allocation *second_allocation = first_allocation + 1;
    second_allocation->addr = first_allocation;
    second_allocation->size = sizeof(struct allocation) * 2;

    /** Initialize the header. */
    struct header *header = (struct header *) heap_addr;
    header->magic_number = valid_magic_number;
    header->allocations = first_allocation;
    header->allocation_count = 2;
    header->heap_size = size;

    return 0;
    }

    void *alloy_malloc(unsigned long int size) {
    return alloy_realloc(NULL, size);
    }

    void *alloy_realloc(void *addr, unsigned long int size) {

    struct header *header = get_header();
    if (header == NULL) {
    return NULL;
    }

    struct allocation *dst_entry = NULL;

    struct allocation *existing_entry = find_allocation(addr);
    if (existing_entry != NULL) {
    dst_entry = existing_entry;
    } else {
    dst_entry = create_allocation();
    if (dst_entry == NULL) {
    return NULL;
    }
    }

    /* align size to 16 bytes. */
    size = ((size + 15) / 16) * 16;

    void *next_addr = find_space_for(size);
    if (next_addr == NULL) {
    remove_invalid_allocation_if_present();
    return NULL;
    }

    if (existing_entry != NULL) {
    alloy_memcpy(next_addr, existing_entry->addr, existing_entry->size);
    }

    dst_entry->addr = next_addr;
    dst_entry->size = size;

    sort_allocations();

    return next_addr;
    }

    void alloy_free(void *addr) {

    struct header *header = get_header();
    if (header == NULL) {
    return;
    }

    struct allocation *allocation = find_allocation(addr);
    if (allocation != NULL) {
    mark_allocation_invalid(allocation);
    /** Invalid allocations go to the back
    * of the allocation table when sorted. */
    sort_allocations();
    /** Now that the free'd allocation is at
    * the back of the table, just reduce the
    * allocation count to get rid of it. */
    header->allocation_count--;
    }
    }


    Here's a test I wrote to verify the functions.



    #include "malloc.h"

    #include <assert.h>
    #include <string.h>

    int main(void) {

    /* Initialize the heap. */

    unsigned char heap[1024];

    int err = alloy_init_heap(heap, sizeof(heap));
    assert(err == 0);

    /* Allocate memory that is definitely out of range of
    * the heap size given. Ensure that it fails. */

    unsigned char *failed_malloc = (unsigned char *) alloy_malloc(2048);
    assert(failed_malloc == NULL);

    /* Allocate three ranges, A, B, and C and allocate
    * them on the heap. */

    unsigned char a_expected[32];
    memset(a_expected, 'a', sizeof(a_expected));

    unsigned char b_expected[64];
    memset(b_expected, 'b', sizeof(b_expected));

    unsigned char b_expected_2[128];
    memset(b_expected_2, 'B', sizeof(b_expected_2));

    unsigned char c_expected[128];
    memset(c_expected, 'c', sizeof(c_expected));

    unsigned char *a = (unsigned char *) alloy_malloc(32);
    assert(a != NULL);
    memset(a, 'a', 32);

    unsigned char *b = (unsigned char *) alloy_malloc(64);
    assert(b != NULL);
    memset(b, 'b', 64);

    unsigned char *c = (unsigned char *) alloy_malloc(128);
    assert(c != NULL);
    memset(c, 'c', 128);

    /* Check to ensure that all memory ranges contain the
    * values that were given to them. Make sure they aren't
    * overlapping. */

    assert(memcmp(a, a_expected, sizeof(a_expected)) == 0);
    assert(memcmp(b, b_expected, sizeof(b_expected)) == 0);
    assert(memcmp(c, c_expected, sizeof(c_expected)) == 0);

    /* The remaining space is 832 (1024 - (32 + 64 + 128)),
    * but is should fail due to the memory used for housekeeping. */

    failed_malloc = (unsigned char *) alloy_malloc(832);
    assert(failed_malloc == NULL);

    /* Resize section B, ensure that all sections still contain
    * their values. */

    b = alloy_realloc(b, 128);
    assert(b != NULL);

    assert(memcmp(a, a_expected, sizeof(a_expected)) == 0);
    assert(memcmp(b, b_expected, sizeof(b_expected)) == 0);
    assert(memcmp(c, c_expected, sizeof(c_expected)) == 0);

    /* Use the rest of the space in B, ensure that all sections
    * still contain their values. */

    memset(b, 'B', 128);

    assert(memcmp(a, a_expected, sizeof(a_expected)) == 0);
    assert(memcmp(b, b_expected_2, sizeof(b_expected_2)) == 0);
    assert(memcmp(c, c_expected, sizeof(c_expected)) == 0);

    /* Release all the memory and verify that a sufficiently
    * large memory section can be allocated again (that would
    * otherwise fail.) */

    /* 896 is 1024 - 128. */
    void *large_section = alloy_malloc(896);
    assert(large_section == NULL);

    alloy_free(a);
    alloy_free(b);
    alloy_free(c);

    large_section = alloy_malloc(896);
    assert(large_section != NULL);

    return EXIT_SUCCESS;
    }


    Here's a test I wrote to benchmark the function (takes 40 seconds on my VM.)



    #include "malloc.h"

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>

    int main(void) {

    /* Initialize the heap (32 MiB). */

    unsigned long int heap_size = 32UL * 1024UL * 1024UL;

    void *heap = malloc(heap_size);
    if (heap == NULL) {
    fprintf(stderr, "Failed to allocate heap space.n");
    return EXIT_FAILURE;
    }

    int err = alloy_init_heap(heap, heap_size);
    if (err != 0) {
    free(heap);
    fprintf(stderr, "Failed to initialize heap.n");
    return EXIT_FAILURE;
    }

    unsigned long int leftover_size = heap_size;

    unsigned long int size_index = 0;

    unsigned long int size_array[8] = {
    8, 16, 32, 64,
    128, 256, 512, 1024
    };

    while (leftover_size > 0) {

    unsigned long int size = size_array[size_index % 8];

    void *addr = alloy_malloc(size);
    if (addr == NULL) {
    break;
    }

    leftover_size -= size;

    size_index++;
    }

    unsigned long int bytes_allocated = heap_size - leftover_size;

    printf("Made %lu allocations.n", size_index);
    printf("Allocated %lu bytes.n", bytes_allocated);
    printf("Used %lu bytes for house keeping.n", leftover_size);

    float space_efficiency = 0.0f;
    space_efficiency += bytes_allocated;
    space_efficiency /= heap_size;
    space_efficiency *= 100.0f;

    printf("Heap space efficiency: %f%%n", space_efficiency);

    free(heap);

    return EXIT_SUCCESS;
    }









    share|improve this question

























      1












      1








      1







      I've written an implementation of malloc, realloc, and free. I wanted to make a malloc implementation that is sufficiently simple and easy to maintain.



      I don't care so much for performance, but I'd like to know if anything stands out that can easily be fixed.



      An explanation of how it works is in the source file. Other malloc reviews I've seen on this site use linked lists, but just note that this implementation does not.



      The prefix alloy is the name of the project, so if you see it in the global functions, that's what it means and you can ignore it.



      Here's malloc.h



      #ifndef alloy_malloc_h
      #define alloy_malloc_h

      #ifdef __cplusplus
      extern "C" {
      #endif

      #ifndef NULL

      /** This value represents an invalid memory address.
      * It contains a value of zero, by default. Zero is
      * usually the standard value of a 'null' pointer. */
      #define NULL ((void *) 0x00)

      #endif

      /** This function must be called for the memory
      * allocation functions to work. It tells malloc
      * where it can find extra memory at.
      * @param addr The base address to allocate memory at.
      * @param size The max number of bytes that can be allocated.
      * This must be at least 64 bytes on 64-bit systems and
      * 32 bytes on 32-bit systems.
      * @returns Zero on success, a negative one on failure. */
      int alloy_init_heap(void *addr, unsigned long int size);

      /** This function allocates a new memory section of a specified size.
      * @param size The number of bytes to allocate.
      * @returns A pointer to the memory section, if one is
      * found that can fit the number of bytes requested.
      * If the memory can't be allocated, then a @ref NULL
      * value is returned instead. */
      void *alloy_malloc(unsigned long int size);

      /** Resizes an existing memory section that was allocated with @ref alloy_malloc.
      * New memory can also be allocated with this function, by passing @p addr with
      * a value of @ref NULL. The data from the previous section is copied to the new section.
      * @param addr The address of the memory section to resize.
      * @param size The number of bytes to resize the section to.
      * @returns A pointer to the newly allocation memory section.
      * If space could not be found for the new section, then a value
      * of @ref NULL is returned instead and the memory section remains unchanged. */
      void *alloy_realloc(void *addr, unsigned long int size);

      /** Releases memory previously allocated with @ref alloy_malloc
      * or @ref alloy_realloc. If the memory address passed to this
      * function does not exist within the heap, then this function has no effect.
      * @param addr The address of the memory section to free. */
      void alloy_free(void *addr);

      #ifdef __cplusplus
      } /* extern "C" */
      #endif

      #endif // alloy_malloc_h


      Here's malloc.c



      #include "malloc.h"

      /* This file contains the malloc implementation for the Alloy project.
      *
      * Here's how it works.
      *
      * The implementation is made up of a 'header' and an allocation table.
      *
      * The header is used for tracking the position of the allocation table
      * entries, then number of allocations, and the size of the heap. The header
      * sits at the very beginning of the heap and remains there for the duration
      * of the library usage.
      *
      * The allocation table is used for keeping track of the memory sections
      * to ensure that they don't overlap. When the heap is first initialized,
      * the allocation table begins immediately after the header. As more allocations
      * are added to the table, it gets resized and moved around the heap. The
      * first two allocations in the allocation table that are made is the heap
      * header allocation and the allocation entry for the allocation table itself.
      * Thus, the allocation table is able to resize itself.
      * */

      /** Used for memory sizes. */
      typedef unsigned long int size_type;

      /** The base address of the heap. */
      unsigned char *heap_addr = NULL;

      /** A single memory allocation. */
      struct allocation {
      /// The memory address that the
      /// allocation is responsible for.
      void *addr;
      /// The number of bytes allocated
      /// for the address.
      size_type size;
      };

      /** Gives the allocation the highest address value
      * and a size of zero. */
      static void mark_allocation_invalid(struct allocation *allocation) {
      allocation->addr = (void *) 0xffffffffffffffff;
      allocation->size = 0;
      }

      /** Indicates whether or not an allocation is invalid. */
      static unsigned int is_invalid_allocation(const struct allocation *allocation) {
      return allocation->addr == ((void *) 0xffffffffffffffff);
      }

      /** Magic number used for sanity checking (= speed of light in m/s). */
      const size_type valid_magic_number = 299792458;

      /** Used at the start of the heap
      * to keep track of the allocation table. */
      struct header {
      /** Used for verifying that the
      * heap was initialized properly. */
      size_type magic_number;
      /** The array of allocations. */
      struct allocation *allocations;
      /** The number of allocations made. */
      size_type allocation_count;
      /** The size of the heap (including this header.) */
      size_type heap_size;
      };

      /** Retrieves the header from the heap address.
      * This function also checks to ensure that the
      * heap has been initialized first.
      * @returns A pointer to the header. */
      static struct header *get_header() {

      if (heap_addr == NULL) {
      return NULL;
      }

      struct header *header = (struct header *) heap_addr;

      if (header->magic_number != valid_magic_number) {
      return NULL;
      }

      return header;
      }

      /** Copies memory from one location to another. */
      static void alloy_memcpy(void *dst, const void *src, size_type size) {

      unsigned char *d = (unsigned char *) dst;

      const unsigned char *s = (const unsigned char *) src;

      for (size_type i = 0; i < size; i++) {
      d[i] = s[i];
      }
      }

      /** Performs a single iteration of the bubblesort algorithm,
      * where the sort key is the allocation address. The number of
      * sorted items is returned. */
      static size_type single_bubblesort(struct allocation *allocations,
      size_type allocation_count) {

      struct allocation *a;
      struct allocation *b;
      struct allocation tmp;

      size_type sorted = 0;

      for (size_type i = 0; (i + 1) < allocation_count; i++) {
      a = &allocations[i];
      b = &allocations[i + 1];
      size_type a_addr = (size_type) a->addr;
      size_type b_addr = (size_type) b->addr;
      if (a_addr > b_addr) {
      tmp.addr = a->addr;
      tmp.size = a->size;
      a->addr = b->addr;
      a->size = b->size;
      b->addr = tmp.addr;
      b->size = tmp.size;
      sorted = 1;
      }
      }

      return sorted;
      }

      /** Sorts the allocations to have ascending
      * starting addresses. Should be called whenever
      * the allocation table is modified. */
      static void sort_allocations() {

      struct header *header = get_header();
      if (header == NULL) {
      return;
      }

      size_type sorted = 0;

      for (;;) {
      sorted = single_bubblesort(header->allocations, header->allocation_count);
      if (!sorted) {
      break;
      }
      }
      }

      /** Locates an address that will fit the specified size.
      * This function does not reserve the address that is found,
      * it just locates an address that will fit the space requested. */
      static void *find_space_for(size_type size) {

      struct header *header = get_header();
      if (header == NULL) {
      return NULL;
      }

      size_type next_addr = (size_type) heap_addr;

      for (size_type i = 0; i < header->allocation_count; i++) {

      struct allocation *allocation = &header->allocations[i];

      size_type allocation_addr = (size_type) allocation->addr;

      if ((next_addr + size) <= allocation_addr) {
      break;
      } else {
      next_addr = allocation_addr + allocation->size;
      }
      }

      if ((next_addr + size) <= (((size_type) heap_addr) + header->heap_size)) {
      return (void *) next_addr;
      }

      return NULL;
      }

      /** Locates an allocation using iterative binary search algorithm,
      * where the search key is the allocation address. */
      static struct allocation *binary_search(struct allocation *allocations,
      size_type left_index,
      size_type right_index,
      size_type addr) {

      while (left_index <= right_index) {

      size_type middle_index = left_index + ((right_index - left_index) / 2);

      size_type candidate_addr = (size_type) allocations[middle_index].addr;

      if (candidate_addr == addr) {
      return &allocations[middle_index];
      } else if (candidate_addr < addr) {
      left_index = middle_index + 1;
      } else {
      right_index = middle_index - 1;
      }
      }

      return NULL;
      }

      /** Locates the allocation entry for a specified address.
      * If no allocation exists for the specified address,
      * then a null pointer is returned. */
      static struct allocation *find_allocation(void *addr) {

      /* Check if addr is NULL so we can just quickly exit. */
      if (addr == NULL) {
      return NULL;
      }

      struct header *header = get_header();

      if (header == NULL) {
      return NULL;
      }

      return binary_search(header->allocations, 0, header->allocation_count, (size_type) addr);
      }

      /** Creates an allocation entry within the allocation table. */
      static struct allocation *create_allocation(void) {

      struct header *header = get_header();

      if (header == NULL) {
      return NULL;
      }

      size_type next_allocations_size = sizeof(struct allocation) * (header->allocation_count + 1);

      struct allocation *next_allocations = find_space_for(next_allocations_size);
      if (next_allocations == NULL) {
      return NULL;
      }

      for (size_type i = 0; i < header->allocation_count; i++) {
      struct allocation *allocation = &header->allocations[i];
      if (allocation->addr == header->allocations) {
      allocation->addr = next_allocations;
      allocation->size = next_allocations_size;
      sort_allocations();
      break;
      }
      }

      alloy_memcpy(next_allocations, header->allocations, sizeof(struct allocation) * header->allocation_count);

      header->allocations = next_allocations;
      header->allocation_count++;

      struct allocation *new_allocation = &next_allocations[header->allocation_count - 1];
      mark_allocation_invalid(new_allocation);
      return new_allocation;
      }

      /** Removes an invalid allocation from the end of the
      * allocation table, if one is present. */
      static void remove_invalid_allocation_if_present(void) {

      struct header *header = get_header();
      if (header == NULL) {
      return;
      }

      if (header->allocation_count <= 0) {
      return;
      }

      struct allocation *last_allocation = &header->allocations[header->allocation_count - 1];
      if (is_invalid_allocation(last_allocation)) {
      header->allocation_count--;
      }
      }

      int alloy_init_heap(void *addr, unsigned long int size) {

      /** Ensure that the heap given can fit a header and two allocations. */
      if (size < (sizeof(struct header) + (sizeof(struct allocation) * 2))) {
      return -1;
      }

      heap_addr = (unsigned char *) addr;

      /** This creates an allocation table for the heap header.
      * This ensures that memory allocations won't allocate space
      * over the header. */
      struct allocation *first_allocation = (struct allocation *) &heap_addr[sizeof(struct header)];
      first_allocation->addr = heap_addr;
      first_allocation->size = sizeof(struct header);

      /** This creates an allocation entry for the allocation table itself.
      * This ensures that memory isn't allocated over the allocation table
      * and it also allows the allocation table to be resized. */
      struct allocation *second_allocation = first_allocation + 1;
      second_allocation->addr = first_allocation;
      second_allocation->size = sizeof(struct allocation) * 2;

      /** Initialize the header. */
      struct header *header = (struct header *) heap_addr;
      header->magic_number = valid_magic_number;
      header->allocations = first_allocation;
      header->allocation_count = 2;
      header->heap_size = size;

      return 0;
      }

      void *alloy_malloc(unsigned long int size) {
      return alloy_realloc(NULL, size);
      }

      void *alloy_realloc(void *addr, unsigned long int size) {

      struct header *header = get_header();
      if (header == NULL) {
      return NULL;
      }

      struct allocation *dst_entry = NULL;

      struct allocation *existing_entry = find_allocation(addr);
      if (existing_entry != NULL) {
      dst_entry = existing_entry;
      } else {
      dst_entry = create_allocation();
      if (dst_entry == NULL) {
      return NULL;
      }
      }

      /* align size to 16 bytes. */
      size = ((size + 15) / 16) * 16;

      void *next_addr = find_space_for(size);
      if (next_addr == NULL) {
      remove_invalid_allocation_if_present();
      return NULL;
      }

      if (existing_entry != NULL) {
      alloy_memcpy(next_addr, existing_entry->addr, existing_entry->size);
      }

      dst_entry->addr = next_addr;
      dst_entry->size = size;

      sort_allocations();

      return next_addr;
      }

      void alloy_free(void *addr) {

      struct header *header = get_header();
      if (header == NULL) {
      return;
      }

      struct allocation *allocation = find_allocation(addr);
      if (allocation != NULL) {
      mark_allocation_invalid(allocation);
      /** Invalid allocations go to the back
      * of the allocation table when sorted. */
      sort_allocations();
      /** Now that the free'd allocation is at
      * the back of the table, just reduce the
      * allocation count to get rid of it. */
      header->allocation_count--;
      }
      }


      Here's a test I wrote to verify the functions.



      #include "malloc.h"

      #include <assert.h>
      #include <string.h>

      int main(void) {

      /* Initialize the heap. */

      unsigned char heap[1024];

      int err = alloy_init_heap(heap, sizeof(heap));
      assert(err == 0);

      /* Allocate memory that is definitely out of range of
      * the heap size given. Ensure that it fails. */

      unsigned char *failed_malloc = (unsigned char *) alloy_malloc(2048);
      assert(failed_malloc == NULL);

      /* Allocate three ranges, A, B, and C and allocate
      * them on the heap. */

      unsigned char a_expected[32];
      memset(a_expected, 'a', sizeof(a_expected));

      unsigned char b_expected[64];
      memset(b_expected, 'b', sizeof(b_expected));

      unsigned char b_expected_2[128];
      memset(b_expected_2, 'B', sizeof(b_expected_2));

      unsigned char c_expected[128];
      memset(c_expected, 'c', sizeof(c_expected));

      unsigned char *a = (unsigned char *) alloy_malloc(32);
      assert(a != NULL);
      memset(a, 'a', 32);

      unsigned char *b = (unsigned char *) alloy_malloc(64);
      assert(b != NULL);
      memset(b, 'b', 64);

      unsigned char *c = (unsigned char *) alloy_malloc(128);
      assert(c != NULL);
      memset(c, 'c', 128);

      /* Check to ensure that all memory ranges contain the
      * values that were given to them. Make sure they aren't
      * overlapping. */

      assert(memcmp(a, a_expected, sizeof(a_expected)) == 0);
      assert(memcmp(b, b_expected, sizeof(b_expected)) == 0);
      assert(memcmp(c, c_expected, sizeof(c_expected)) == 0);

      /* The remaining space is 832 (1024 - (32 + 64 + 128)),
      * but is should fail due to the memory used for housekeeping. */

      failed_malloc = (unsigned char *) alloy_malloc(832);
      assert(failed_malloc == NULL);

      /* Resize section B, ensure that all sections still contain
      * their values. */

      b = alloy_realloc(b, 128);
      assert(b != NULL);

      assert(memcmp(a, a_expected, sizeof(a_expected)) == 0);
      assert(memcmp(b, b_expected, sizeof(b_expected)) == 0);
      assert(memcmp(c, c_expected, sizeof(c_expected)) == 0);

      /* Use the rest of the space in B, ensure that all sections
      * still contain their values. */

      memset(b, 'B', 128);

      assert(memcmp(a, a_expected, sizeof(a_expected)) == 0);
      assert(memcmp(b, b_expected_2, sizeof(b_expected_2)) == 0);
      assert(memcmp(c, c_expected, sizeof(c_expected)) == 0);

      /* Release all the memory and verify that a sufficiently
      * large memory section can be allocated again (that would
      * otherwise fail.) */

      /* 896 is 1024 - 128. */
      void *large_section = alloy_malloc(896);
      assert(large_section == NULL);

      alloy_free(a);
      alloy_free(b);
      alloy_free(c);

      large_section = alloy_malloc(896);
      assert(large_section != NULL);

      return EXIT_SUCCESS;
      }


      Here's a test I wrote to benchmark the function (takes 40 seconds on my VM.)



      #include "malloc.h"

      #include <stdio.h>
      #include <stdlib.h>
      #include <string.h>

      int main(void) {

      /* Initialize the heap (32 MiB). */

      unsigned long int heap_size = 32UL * 1024UL * 1024UL;

      void *heap = malloc(heap_size);
      if (heap == NULL) {
      fprintf(stderr, "Failed to allocate heap space.n");
      return EXIT_FAILURE;
      }

      int err = alloy_init_heap(heap, heap_size);
      if (err != 0) {
      free(heap);
      fprintf(stderr, "Failed to initialize heap.n");
      return EXIT_FAILURE;
      }

      unsigned long int leftover_size = heap_size;

      unsigned long int size_index = 0;

      unsigned long int size_array[8] = {
      8, 16, 32, 64,
      128, 256, 512, 1024
      };

      while (leftover_size > 0) {

      unsigned long int size = size_array[size_index % 8];

      void *addr = alloy_malloc(size);
      if (addr == NULL) {
      break;
      }

      leftover_size -= size;

      size_index++;
      }

      unsigned long int bytes_allocated = heap_size - leftover_size;

      printf("Made %lu allocations.n", size_index);
      printf("Allocated %lu bytes.n", bytes_allocated);
      printf("Used %lu bytes for house keeping.n", leftover_size);

      float space_efficiency = 0.0f;
      space_efficiency += bytes_allocated;
      space_efficiency /= heap_size;
      space_efficiency *= 100.0f;

      printf("Heap space efficiency: %f%%n", space_efficiency);

      free(heap);

      return EXIT_SUCCESS;
      }









      share|improve this question













      I've written an implementation of malloc, realloc, and free. I wanted to make a malloc implementation that is sufficiently simple and easy to maintain.



      I don't care so much for performance, but I'd like to know if anything stands out that can easily be fixed.



      An explanation of how it works is in the source file. Other malloc reviews I've seen on this site use linked lists, but just note that this implementation does not.



      The prefix alloy is the name of the project, so if you see it in the global functions, that's what it means and you can ignore it.



      Here's malloc.h



      #ifndef alloy_malloc_h
      #define alloy_malloc_h

      #ifdef __cplusplus
      extern "C" {
      #endif

      #ifndef NULL

      /** This value represents an invalid memory address.
      * It contains a value of zero, by default. Zero is
      * usually the standard value of a 'null' pointer. */
      #define NULL ((void *) 0x00)

      #endif

      /** This function must be called for the memory
      * allocation functions to work. It tells malloc
      * where it can find extra memory at.
      * @param addr The base address to allocate memory at.
      * @param size The max number of bytes that can be allocated.
      * This must be at least 64 bytes on 64-bit systems and
      * 32 bytes on 32-bit systems.
      * @returns Zero on success, a negative one on failure. */
      int alloy_init_heap(void *addr, unsigned long int size);

      /** This function allocates a new memory section of a specified size.
      * @param size The number of bytes to allocate.
      * @returns A pointer to the memory section, if one is
      * found that can fit the number of bytes requested.
      * If the memory can't be allocated, then a @ref NULL
      * value is returned instead. */
      void *alloy_malloc(unsigned long int size);

      /** Resizes an existing memory section that was allocated with @ref alloy_malloc.
      * New memory can also be allocated with this function, by passing @p addr with
      * a value of @ref NULL. The data from the previous section is copied to the new section.
      * @param addr The address of the memory section to resize.
      * @param size The number of bytes to resize the section to.
      * @returns A pointer to the newly allocation memory section.
      * If space could not be found for the new section, then a value
      * of @ref NULL is returned instead and the memory section remains unchanged. */
      void *alloy_realloc(void *addr, unsigned long int size);

      /** Releases memory previously allocated with @ref alloy_malloc
      * or @ref alloy_realloc. If the memory address passed to this
      * function does not exist within the heap, then this function has no effect.
      * @param addr The address of the memory section to free. */
      void alloy_free(void *addr);

      #ifdef __cplusplus
      } /* extern "C" */
      #endif

      #endif // alloy_malloc_h


      Here's malloc.c



      #include "malloc.h"

      /* This file contains the malloc implementation for the Alloy project.
      *
      * Here's how it works.
      *
      * The implementation is made up of a 'header' and an allocation table.
      *
      * The header is used for tracking the position of the allocation table
      * entries, then number of allocations, and the size of the heap. The header
      * sits at the very beginning of the heap and remains there for the duration
      * of the library usage.
      *
      * The allocation table is used for keeping track of the memory sections
      * to ensure that they don't overlap. When the heap is first initialized,
      * the allocation table begins immediately after the header. As more allocations
      * are added to the table, it gets resized and moved around the heap. The
      * first two allocations in the allocation table that are made is the heap
      * header allocation and the allocation entry for the allocation table itself.
      * Thus, the allocation table is able to resize itself.
      * */

      /** Used for memory sizes. */
      typedef unsigned long int size_type;

      /** The base address of the heap. */
      unsigned char *heap_addr = NULL;

      /** A single memory allocation. */
      struct allocation {
      /// The memory address that the
      /// allocation is responsible for.
      void *addr;
      /// The number of bytes allocated
      /// for the address.
      size_type size;
      };

      /** Gives the allocation the highest address value
      * and a size of zero. */
      static void mark_allocation_invalid(struct allocation *allocation) {
      allocation->addr = (void *) 0xffffffffffffffff;
      allocation->size = 0;
      }

      /** Indicates whether or not an allocation is invalid. */
      static unsigned int is_invalid_allocation(const struct allocation *allocation) {
      return allocation->addr == ((void *) 0xffffffffffffffff);
      }

      /** Magic number used for sanity checking (= speed of light in m/s). */
      const size_type valid_magic_number = 299792458;

      /** Used at the start of the heap
      * to keep track of the allocation table. */
      struct header {
      /** Used for verifying that the
      * heap was initialized properly. */
      size_type magic_number;
      /** The array of allocations. */
      struct allocation *allocations;
      /** The number of allocations made. */
      size_type allocation_count;
      /** The size of the heap (including this header.) */
      size_type heap_size;
      };

      /** Retrieves the header from the heap address.
      * This function also checks to ensure that the
      * heap has been initialized first.
      * @returns A pointer to the header. */
      static struct header *get_header() {

      if (heap_addr == NULL) {
      return NULL;
      }

      struct header *header = (struct header *) heap_addr;

      if (header->magic_number != valid_magic_number) {
      return NULL;
      }

      return header;
      }

      /** Copies memory from one location to another. */
      static void alloy_memcpy(void *dst, const void *src, size_type size) {

      unsigned char *d = (unsigned char *) dst;

      const unsigned char *s = (const unsigned char *) src;

      for (size_type i = 0; i < size; i++) {
      d[i] = s[i];
      }
      }

      /** Performs a single iteration of the bubblesort algorithm,
      * where the sort key is the allocation address. The number of
      * sorted items is returned. */
      static size_type single_bubblesort(struct allocation *allocations,
      size_type allocation_count) {

      struct allocation *a;
      struct allocation *b;
      struct allocation tmp;

      size_type sorted = 0;

      for (size_type i = 0; (i + 1) < allocation_count; i++) {
      a = &allocations[i];
      b = &allocations[i + 1];
      size_type a_addr = (size_type) a->addr;
      size_type b_addr = (size_type) b->addr;
      if (a_addr > b_addr) {
      tmp.addr = a->addr;
      tmp.size = a->size;
      a->addr = b->addr;
      a->size = b->size;
      b->addr = tmp.addr;
      b->size = tmp.size;
      sorted = 1;
      }
      }

      return sorted;
      }

      /** Sorts the allocations to have ascending
      * starting addresses. Should be called whenever
      * the allocation table is modified. */
      static void sort_allocations() {

      struct header *header = get_header();
      if (header == NULL) {
      return;
      }

      size_type sorted = 0;

      for (;;) {
      sorted = single_bubblesort(header->allocations, header->allocation_count);
      if (!sorted) {
      break;
      }
      }
      }

      /** Locates an address that will fit the specified size.
      * This function does not reserve the address that is found,
      * it just locates an address that will fit the space requested. */
      static void *find_space_for(size_type size) {

      struct header *header = get_header();
      if (header == NULL) {
      return NULL;
      }

      size_type next_addr = (size_type) heap_addr;

      for (size_type i = 0; i < header->allocation_count; i++) {

      struct allocation *allocation = &header->allocations[i];

      size_type allocation_addr = (size_type) allocation->addr;

      if ((next_addr + size) <= allocation_addr) {
      break;
      } else {
      next_addr = allocation_addr + allocation->size;
      }
      }

      if ((next_addr + size) <= (((size_type) heap_addr) + header->heap_size)) {
      return (void *) next_addr;
      }

      return NULL;
      }

      /** Locates an allocation using iterative binary search algorithm,
      * where the search key is the allocation address. */
      static struct allocation *binary_search(struct allocation *allocations,
      size_type left_index,
      size_type right_index,
      size_type addr) {

      while (left_index <= right_index) {

      size_type middle_index = left_index + ((right_index - left_index) / 2);

      size_type candidate_addr = (size_type) allocations[middle_index].addr;

      if (candidate_addr == addr) {
      return &allocations[middle_index];
      } else if (candidate_addr < addr) {
      left_index = middle_index + 1;
      } else {
      right_index = middle_index - 1;
      }
      }

      return NULL;
      }

      /** Locates the allocation entry for a specified address.
      * If no allocation exists for the specified address,
      * then a null pointer is returned. */
      static struct allocation *find_allocation(void *addr) {

      /* Check if addr is NULL so we can just quickly exit. */
      if (addr == NULL) {
      return NULL;
      }

      struct header *header = get_header();

      if (header == NULL) {
      return NULL;
      }

      return binary_search(header->allocations, 0, header->allocation_count, (size_type) addr);
      }

      /** Creates an allocation entry within the allocation table. */
      static struct allocation *create_allocation(void) {

      struct header *header = get_header();

      if (header == NULL) {
      return NULL;
      }

      size_type next_allocations_size = sizeof(struct allocation) * (header->allocation_count + 1);

      struct allocation *next_allocations = find_space_for(next_allocations_size);
      if (next_allocations == NULL) {
      return NULL;
      }

      for (size_type i = 0; i < header->allocation_count; i++) {
      struct allocation *allocation = &header->allocations[i];
      if (allocation->addr == header->allocations) {
      allocation->addr = next_allocations;
      allocation->size = next_allocations_size;
      sort_allocations();
      break;
      }
      }

      alloy_memcpy(next_allocations, header->allocations, sizeof(struct allocation) * header->allocation_count);

      header->allocations = next_allocations;
      header->allocation_count++;

      struct allocation *new_allocation = &next_allocations[header->allocation_count - 1];
      mark_allocation_invalid(new_allocation);
      return new_allocation;
      }

      /** Removes an invalid allocation from the end of the
      * allocation table, if one is present. */
      static void remove_invalid_allocation_if_present(void) {

      struct header *header = get_header();
      if (header == NULL) {
      return;
      }

      if (header->allocation_count <= 0) {
      return;
      }

      struct allocation *last_allocation = &header->allocations[header->allocation_count - 1];
      if (is_invalid_allocation(last_allocation)) {
      header->allocation_count--;
      }
      }

      int alloy_init_heap(void *addr, unsigned long int size) {

      /** Ensure that the heap given can fit a header and two allocations. */
      if (size < (sizeof(struct header) + (sizeof(struct allocation) * 2))) {
      return -1;
      }

      heap_addr = (unsigned char *) addr;

      /** This creates an allocation table for the heap header.
      * This ensures that memory allocations won't allocate space
      * over the header. */
      struct allocation *first_allocation = (struct allocation *) &heap_addr[sizeof(struct header)];
      first_allocation->addr = heap_addr;
      first_allocation->size = sizeof(struct header);

      /** This creates an allocation entry for the allocation table itself.
      * This ensures that memory isn't allocated over the allocation table
      * and it also allows the allocation table to be resized. */
      struct allocation *second_allocation = first_allocation + 1;
      second_allocation->addr = first_allocation;
      second_allocation->size = sizeof(struct allocation) * 2;

      /** Initialize the header. */
      struct header *header = (struct header *) heap_addr;
      header->magic_number = valid_magic_number;
      header->allocations = first_allocation;
      header->allocation_count = 2;
      header->heap_size = size;

      return 0;
      }

      void *alloy_malloc(unsigned long int size) {
      return alloy_realloc(NULL, size);
      }

      void *alloy_realloc(void *addr, unsigned long int size) {

      struct header *header = get_header();
      if (header == NULL) {
      return NULL;
      }

      struct allocation *dst_entry = NULL;

      struct allocation *existing_entry = find_allocation(addr);
      if (existing_entry != NULL) {
      dst_entry = existing_entry;
      } else {
      dst_entry = create_allocation();
      if (dst_entry == NULL) {
      return NULL;
      }
      }

      /* align size to 16 bytes. */
      size = ((size + 15) / 16) * 16;

      void *next_addr = find_space_for(size);
      if (next_addr == NULL) {
      remove_invalid_allocation_if_present();
      return NULL;
      }

      if (existing_entry != NULL) {
      alloy_memcpy(next_addr, existing_entry->addr, existing_entry->size);
      }

      dst_entry->addr = next_addr;
      dst_entry->size = size;

      sort_allocations();

      return next_addr;
      }

      void alloy_free(void *addr) {

      struct header *header = get_header();
      if (header == NULL) {
      return;
      }

      struct allocation *allocation = find_allocation(addr);
      if (allocation != NULL) {
      mark_allocation_invalid(allocation);
      /** Invalid allocations go to the back
      * of the allocation table when sorted. */
      sort_allocations();
      /** Now that the free'd allocation is at
      * the back of the table, just reduce the
      * allocation count to get rid of it. */
      header->allocation_count--;
      }
      }


      Here's a test I wrote to verify the functions.



      #include "malloc.h"

      #include <assert.h>
      #include <string.h>

      int main(void) {

      /* Initialize the heap. */

      unsigned char heap[1024];

      int err = alloy_init_heap(heap, sizeof(heap));
      assert(err == 0);

      /* Allocate memory that is definitely out of range of
      * the heap size given. Ensure that it fails. */

      unsigned char *failed_malloc = (unsigned char *) alloy_malloc(2048);
      assert(failed_malloc == NULL);

      /* Allocate three ranges, A, B, and C and allocate
      * them on the heap. */

      unsigned char a_expected[32];
      memset(a_expected, 'a', sizeof(a_expected));

      unsigned char b_expected[64];
      memset(b_expected, 'b', sizeof(b_expected));

      unsigned char b_expected_2[128];
      memset(b_expected_2, 'B', sizeof(b_expected_2));

      unsigned char c_expected[128];
      memset(c_expected, 'c', sizeof(c_expected));

      unsigned char *a = (unsigned char *) alloy_malloc(32);
      assert(a != NULL);
      memset(a, 'a', 32);

      unsigned char *b = (unsigned char *) alloy_malloc(64);
      assert(b != NULL);
      memset(b, 'b', 64);

      unsigned char *c = (unsigned char *) alloy_malloc(128);
      assert(c != NULL);
      memset(c, 'c', 128);

      /* Check to ensure that all memory ranges contain the
      * values that were given to them. Make sure they aren't
      * overlapping. */

      assert(memcmp(a, a_expected, sizeof(a_expected)) == 0);
      assert(memcmp(b, b_expected, sizeof(b_expected)) == 0);
      assert(memcmp(c, c_expected, sizeof(c_expected)) == 0);

      /* The remaining space is 832 (1024 - (32 + 64 + 128)),
      * but is should fail due to the memory used for housekeeping. */

      failed_malloc = (unsigned char *) alloy_malloc(832);
      assert(failed_malloc == NULL);

      /* Resize section B, ensure that all sections still contain
      * their values. */

      b = alloy_realloc(b, 128);
      assert(b != NULL);

      assert(memcmp(a, a_expected, sizeof(a_expected)) == 0);
      assert(memcmp(b, b_expected, sizeof(b_expected)) == 0);
      assert(memcmp(c, c_expected, sizeof(c_expected)) == 0);

      /* Use the rest of the space in B, ensure that all sections
      * still contain their values. */

      memset(b, 'B', 128);

      assert(memcmp(a, a_expected, sizeof(a_expected)) == 0);
      assert(memcmp(b, b_expected_2, sizeof(b_expected_2)) == 0);
      assert(memcmp(c, c_expected, sizeof(c_expected)) == 0);

      /* Release all the memory and verify that a sufficiently
      * large memory section can be allocated again (that would
      * otherwise fail.) */

      /* 896 is 1024 - 128. */
      void *large_section = alloy_malloc(896);
      assert(large_section == NULL);

      alloy_free(a);
      alloy_free(b);
      alloy_free(c);

      large_section = alloy_malloc(896);
      assert(large_section != NULL);

      return EXIT_SUCCESS;
      }


      Here's a test I wrote to benchmark the function (takes 40 seconds on my VM.)



      #include "malloc.h"

      #include <stdio.h>
      #include <stdlib.h>
      #include <string.h>

      int main(void) {

      /* Initialize the heap (32 MiB). */

      unsigned long int heap_size = 32UL * 1024UL * 1024UL;

      void *heap = malloc(heap_size);
      if (heap == NULL) {
      fprintf(stderr, "Failed to allocate heap space.n");
      return EXIT_FAILURE;
      }

      int err = alloy_init_heap(heap, heap_size);
      if (err != 0) {
      free(heap);
      fprintf(stderr, "Failed to initialize heap.n");
      return EXIT_FAILURE;
      }

      unsigned long int leftover_size = heap_size;

      unsigned long int size_index = 0;

      unsigned long int size_array[8] = {
      8, 16, 32, 64,
      128, 256, 512, 1024
      };

      while (leftover_size > 0) {

      unsigned long int size = size_array[size_index % 8];

      void *addr = alloy_malloc(size);
      if (addr == NULL) {
      break;
      }

      leftover_size -= size;

      size_index++;
      }

      unsigned long int bytes_allocated = heap_size - leftover_size;

      printf("Made %lu allocations.n", size_index);
      printf("Allocated %lu bytes.n", bytes_allocated);
      printf("Used %lu bytes for house keeping.n", leftover_size);

      float space_efficiency = 0.0f;
      space_efficiency += bytes_allocated;
      space_efficiency /= heap_size;
      space_efficiency *= 100.0f;

      printf("Heap space efficiency: %f%%n", space_efficiency);

      free(heap);

      return EXIT_SUCCESS;
      }






      c memory-management






      share|improve this question













      share|improve this question











      share|improve this question




      share|improve this question










      asked Dec 19 at 15:25









      tay10r

      1548




      1548






















          1 Answer
          1






          active

          oldest

          votes


















          0














          0x00 can simply be written 0; the hexadecimal notation doesn't buy you anything here.



          In your function signatures such as



          int alloy_init_heap(void *addr, unsigned long int size);


          you should rearrange your code so that you can use your size_type instead of rewriting unsigned long int.



          For your structs (allocation, header, etc.) use typedef so that you don't have to re-write struct on instantiation.



          In alloy_memcpy, rather than char-by-char copy, consider doing word-by-word copy. 64-bit copies will be much more efficient here.



          This loop:



          for (;;) {
          sorted = single_bubblesort(header->allocations, header->allocation_count);
          if (!sorted) {
          break;
          }
          }


          can simply be



          while (single_bubblesort(header->allocations, header->allocation_count));


          This:



          if ((next_addr + size) <= allocation_addr) {


          and this:



          if ((next_addr + size) <= (((size_type) heap_addr) + header->heap_size)) {


          do not need inner parens, due to operator precedence.



          Rather than writing unsigned char everywhere, consider using uint8_t from stdint.h.



          This:



          float space_efficiency = 0.0f;
          space_efficiency += bytes_allocated;
          space_efficiency /= heap_size;
          space_efficiency *= 100.0f;


          can simply be



          float space_efficiency = bytes_allocated*100. / heap_size;


          This array:



          unsigned long int size_array[8] = {
          8, 16, 32, 64,
          128, 256, 512, 1024
          };


          doesn't need to be initialized literally. You can do:



          for (int i = 0; i < 8; i++)
          size_array[i] = 8 << i;


          Whenever you write 0xffffffffffffffff, you can replace it with -1.



          In this function (and similar ones elsewhere):



          static struct allocation *create_allocation(void) {


          (void) is redundant. Just write ().






          share|improve this answer























          • To typedef a struct just clobbers the namespace.I believe the way I wrote the loop shows the meaning behind the return value, making it a bit more readable. I figured GCC would optimize the memcpy function.
            – tay10r
            Dec 19 at 16:00










          • (void) and () don't mean the same thing, and some versions of MSVC will barf when it comes across () in a function declaration. Overall I was hoping to have more feedback about the malloc algorithm and the implementation. I feel like this answer gives more about the syntax usage then anything about the actual implementation.
            – tay10r
            Dec 19 at 16:05






          • 1




            I'm not saying your usage is invalid or conjured. I'm suggesting ways to make your code more concise. You made a generic request for anything that 'stands out'; the above stand out to me. If I find more time I'll review your algorithm.
            – Reinderien
            Dec 19 at 17:43






          • 2




            @tay10r (void) and () in a function declaration do not mean the same thing. Yet in a function definition, they do.
            – chux
            Dec 19 at 22:02






          • 1




            @Reinderien () in a function declaration has meant "no info about the arguments" which does allow calling the function with any number of arguments - rightly or wrongly - without warning. A C89 throwback.
            – chux
            Dec 19 at 22:12











          Your Answer





          StackExchange.ifUsing("editor", function () {
          return StackExchange.using("mathjaxEditing", function () {
          StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
          StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
          });
          });
          }, "mathjax-editing");

          StackExchange.ifUsing("editor", function () {
          StackExchange.using("externalEditor", function () {
          StackExchange.using("snippets", function () {
          StackExchange.snippets.init();
          });
          });
          }, "code-snippets");

          StackExchange.ready(function() {
          var channelOptions = {
          tags: "".split(" "),
          id: "196"
          };
          initTagRenderer("".split(" "), "".split(" "), channelOptions);

          StackExchange.using("externalEditor", function() {
          // Have to fire editor after snippets, if snippets enabled
          if (StackExchange.settings.snippets.snippetsEnabled) {
          StackExchange.using("snippets", function() {
          createEditor();
          });
          }
          else {
          createEditor();
          }
          });

          function createEditor() {
          StackExchange.prepareEditor({
          heartbeatType: 'answer',
          autoActivateHeartbeat: false,
          convertImagesToLinks: false,
          noModals: true,
          showLowRepImageUploadWarning: true,
          reputationToPostImages: null,
          bindNavPrevention: true,
          postfix: "",
          imageUploader: {
          brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
          contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
          allowUrls: true
          },
          onDemand: true,
          discardSelector: ".discard-answer"
          ,immediatelyShowMarkdownHelp:true
          });


          }
          });














          draft saved

          draft discarded


















          StackExchange.ready(
          function () {
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f209981%2fsimple-malloc-implementation%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown

























          1 Answer
          1






          active

          oldest

          votes








          1 Answer
          1






          active

          oldest

          votes









          active

          oldest

          votes






          active

          oldest

          votes









          0














          0x00 can simply be written 0; the hexadecimal notation doesn't buy you anything here.



          In your function signatures such as



          int alloy_init_heap(void *addr, unsigned long int size);


          you should rearrange your code so that you can use your size_type instead of rewriting unsigned long int.



          For your structs (allocation, header, etc.) use typedef so that you don't have to re-write struct on instantiation.



          In alloy_memcpy, rather than char-by-char copy, consider doing word-by-word copy. 64-bit copies will be much more efficient here.



          This loop:



          for (;;) {
          sorted = single_bubblesort(header->allocations, header->allocation_count);
          if (!sorted) {
          break;
          }
          }


          can simply be



          while (single_bubblesort(header->allocations, header->allocation_count));


          This:



          if ((next_addr + size) <= allocation_addr) {


          and this:



          if ((next_addr + size) <= (((size_type) heap_addr) + header->heap_size)) {


          do not need inner parens, due to operator precedence.



          Rather than writing unsigned char everywhere, consider using uint8_t from stdint.h.



          This:



          float space_efficiency = 0.0f;
          space_efficiency += bytes_allocated;
          space_efficiency /= heap_size;
          space_efficiency *= 100.0f;


          can simply be



          float space_efficiency = bytes_allocated*100. / heap_size;


          This array:



          unsigned long int size_array[8] = {
          8, 16, 32, 64,
          128, 256, 512, 1024
          };


          doesn't need to be initialized literally. You can do:



          for (int i = 0; i < 8; i++)
          size_array[i] = 8 << i;


          Whenever you write 0xffffffffffffffff, you can replace it with -1.



          In this function (and similar ones elsewhere):



          static struct allocation *create_allocation(void) {


          (void) is redundant. Just write ().






          share|improve this answer























          • To typedef a struct just clobbers the namespace.I believe the way I wrote the loop shows the meaning behind the return value, making it a bit more readable. I figured GCC would optimize the memcpy function.
            – tay10r
            Dec 19 at 16:00










          • (void) and () don't mean the same thing, and some versions of MSVC will barf when it comes across () in a function declaration. Overall I was hoping to have more feedback about the malloc algorithm and the implementation. I feel like this answer gives more about the syntax usage then anything about the actual implementation.
            – tay10r
            Dec 19 at 16:05






          • 1




            I'm not saying your usage is invalid or conjured. I'm suggesting ways to make your code more concise. You made a generic request for anything that 'stands out'; the above stand out to me. If I find more time I'll review your algorithm.
            – Reinderien
            Dec 19 at 17:43






          • 2




            @tay10r (void) and () in a function declaration do not mean the same thing. Yet in a function definition, they do.
            – chux
            Dec 19 at 22:02






          • 1




            @Reinderien () in a function declaration has meant "no info about the arguments" which does allow calling the function with any number of arguments - rightly or wrongly - without warning. A C89 throwback.
            – chux
            Dec 19 at 22:12
















          0














          0x00 can simply be written 0; the hexadecimal notation doesn't buy you anything here.



          In your function signatures such as



          int alloy_init_heap(void *addr, unsigned long int size);


          you should rearrange your code so that you can use your size_type instead of rewriting unsigned long int.



          For your structs (allocation, header, etc.) use typedef so that you don't have to re-write struct on instantiation.



          In alloy_memcpy, rather than char-by-char copy, consider doing word-by-word copy. 64-bit copies will be much more efficient here.



          This loop:



          for (;;) {
          sorted = single_bubblesort(header->allocations, header->allocation_count);
          if (!sorted) {
          break;
          }
          }


          can simply be



          while (single_bubblesort(header->allocations, header->allocation_count));


          This:



          if ((next_addr + size) <= allocation_addr) {


          and this:



          if ((next_addr + size) <= (((size_type) heap_addr) + header->heap_size)) {


          do not need inner parens, due to operator precedence.



          Rather than writing unsigned char everywhere, consider using uint8_t from stdint.h.



          This:



          float space_efficiency = 0.0f;
          space_efficiency += bytes_allocated;
          space_efficiency /= heap_size;
          space_efficiency *= 100.0f;


          can simply be



          float space_efficiency = bytes_allocated*100. / heap_size;


          This array:



          unsigned long int size_array[8] = {
          8, 16, 32, 64,
          128, 256, 512, 1024
          };


          doesn't need to be initialized literally. You can do:



          for (int i = 0; i < 8; i++)
          size_array[i] = 8 << i;


          Whenever you write 0xffffffffffffffff, you can replace it with -1.



          In this function (and similar ones elsewhere):



          static struct allocation *create_allocation(void) {


          (void) is redundant. Just write ().






          share|improve this answer























          • To typedef a struct just clobbers the namespace.I believe the way I wrote the loop shows the meaning behind the return value, making it a bit more readable. I figured GCC would optimize the memcpy function.
            – tay10r
            Dec 19 at 16:00










          • (void) and () don't mean the same thing, and some versions of MSVC will barf when it comes across () in a function declaration. Overall I was hoping to have more feedback about the malloc algorithm and the implementation. I feel like this answer gives more about the syntax usage then anything about the actual implementation.
            – tay10r
            Dec 19 at 16:05






          • 1




            I'm not saying your usage is invalid or conjured. I'm suggesting ways to make your code more concise. You made a generic request for anything that 'stands out'; the above stand out to me. If I find more time I'll review your algorithm.
            – Reinderien
            Dec 19 at 17:43






          • 2




            @tay10r (void) and () in a function declaration do not mean the same thing. Yet in a function definition, they do.
            – chux
            Dec 19 at 22:02






          • 1




            @Reinderien () in a function declaration has meant "no info about the arguments" which does allow calling the function with any number of arguments - rightly or wrongly - without warning. A C89 throwback.
            – chux
            Dec 19 at 22:12














          0












          0








          0






          0x00 can simply be written 0; the hexadecimal notation doesn't buy you anything here.



          In your function signatures such as



          int alloy_init_heap(void *addr, unsigned long int size);


          you should rearrange your code so that you can use your size_type instead of rewriting unsigned long int.



          For your structs (allocation, header, etc.) use typedef so that you don't have to re-write struct on instantiation.



          In alloy_memcpy, rather than char-by-char copy, consider doing word-by-word copy. 64-bit copies will be much more efficient here.



          This loop:



          for (;;) {
          sorted = single_bubblesort(header->allocations, header->allocation_count);
          if (!sorted) {
          break;
          }
          }


          can simply be



          while (single_bubblesort(header->allocations, header->allocation_count));


          This:



          if ((next_addr + size) <= allocation_addr) {


          and this:



          if ((next_addr + size) <= (((size_type) heap_addr) + header->heap_size)) {


          do not need inner parens, due to operator precedence.



          Rather than writing unsigned char everywhere, consider using uint8_t from stdint.h.



          This:



          float space_efficiency = 0.0f;
          space_efficiency += bytes_allocated;
          space_efficiency /= heap_size;
          space_efficiency *= 100.0f;


          can simply be



          float space_efficiency = bytes_allocated*100. / heap_size;


          This array:



          unsigned long int size_array[8] = {
          8, 16, 32, 64,
          128, 256, 512, 1024
          };


          doesn't need to be initialized literally. You can do:



          for (int i = 0; i < 8; i++)
          size_array[i] = 8 << i;


          Whenever you write 0xffffffffffffffff, you can replace it with -1.



          In this function (and similar ones elsewhere):



          static struct allocation *create_allocation(void) {


          (void) is redundant. Just write ().






          share|improve this answer














          0x00 can simply be written 0; the hexadecimal notation doesn't buy you anything here.



          In your function signatures such as



          int alloy_init_heap(void *addr, unsigned long int size);


          you should rearrange your code so that you can use your size_type instead of rewriting unsigned long int.



          For your structs (allocation, header, etc.) use typedef so that you don't have to re-write struct on instantiation.



          In alloy_memcpy, rather than char-by-char copy, consider doing word-by-word copy. 64-bit copies will be much more efficient here.



          This loop:



          for (;;) {
          sorted = single_bubblesort(header->allocations, header->allocation_count);
          if (!sorted) {
          break;
          }
          }


          can simply be



          while (single_bubblesort(header->allocations, header->allocation_count));


          This:



          if ((next_addr + size) <= allocation_addr) {


          and this:



          if ((next_addr + size) <= (((size_type) heap_addr) + header->heap_size)) {


          do not need inner parens, due to operator precedence.



          Rather than writing unsigned char everywhere, consider using uint8_t from stdint.h.



          This:



          float space_efficiency = 0.0f;
          space_efficiency += bytes_allocated;
          space_efficiency /= heap_size;
          space_efficiency *= 100.0f;


          can simply be



          float space_efficiency = bytes_allocated*100. / heap_size;


          This array:



          unsigned long int size_array[8] = {
          8, 16, 32, 64,
          128, 256, 512, 1024
          };


          doesn't need to be initialized literally. You can do:



          for (int i = 0; i < 8; i++)
          size_array[i] = 8 << i;


          Whenever you write 0xffffffffffffffff, you can replace it with -1.



          In this function (and similar ones elsewhere):



          static struct allocation *create_allocation(void) {


          (void) is redundant. Just write ().







          share|improve this answer














          share|improve this answer



          share|improve this answer








          edited Dec 19 at 15:55

























          answered Dec 19 at 15:47









          Reinderien

          2,684619




          2,684619












          • To typedef a struct just clobbers the namespace.I believe the way I wrote the loop shows the meaning behind the return value, making it a bit more readable. I figured GCC would optimize the memcpy function.
            – tay10r
            Dec 19 at 16:00










          • (void) and () don't mean the same thing, and some versions of MSVC will barf when it comes across () in a function declaration. Overall I was hoping to have more feedback about the malloc algorithm and the implementation. I feel like this answer gives more about the syntax usage then anything about the actual implementation.
            – tay10r
            Dec 19 at 16:05






          • 1




            I'm not saying your usage is invalid or conjured. I'm suggesting ways to make your code more concise. You made a generic request for anything that 'stands out'; the above stand out to me. If I find more time I'll review your algorithm.
            – Reinderien
            Dec 19 at 17:43






          • 2




            @tay10r (void) and () in a function declaration do not mean the same thing. Yet in a function definition, they do.
            – chux
            Dec 19 at 22:02






          • 1




            @Reinderien () in a function declaration has meant "no info about the arguments" which does allow calling the function with any number of arguments - rightly or wrongly - without warning. A C89 throwback.
            – chux
            Dec 19 at 22:12


















          • To typedef a struct just clobbers the namespace.I believe the way I wrote the loop shows the meaning behind the return value, making it a bit more readable. I figured GCC would optimize the memcpy function.
            – tay10r
            Dec 19 at 16:00










          • (void) and () don't mean the same thing, and some versions of MSVC will barf when it comes across () in a function declaration. Overall I was hoping to have more feedback about the malloc algorithm and the implementation. I feel like this answer gives more about the syntax usage then anything about the actual implementation.
            – tay10r
            Dec 19 at 16:05






          • 1




            I'm not saying your usage is invalid or conjured. I'm suggesting ways to make your code more concise. You made a generic request for anything that 'stands out'; the above stand out to me. If I find more time I'll review your algorithm.
            – Reinderien
            Dec 19 at 17:43






          • 2




            @tay10r (void) and () in a function declaration do not mean the same thing. Yet in a function definition, they do.
            – chux
            Dec 19 at 22:02






          • 1




            @Reinderien () in a function declaration has meant "no info about the arguments" which does allow calling the function with any number of arguments - rightly or wrongly - without warning. A C89 throwback.
            – chux
            Dec 19 at 22:12
















          To typedef a struct just clobbers the namespace.I believe the way I wrote the loop shows the meaning behind the return value, making it a bit more readable. I figured GCC would optimize the memcpy function.
          – tay10r
          Dec 19 at 16:00




          To typedef a struct just clobbers the namespace.I believe the way I wrote the loop shows the meaning behind the return value, making it a bit more readable. I figured GCC would optimize the memcpy function.
          – tay10r
          Dec 19 at 16:00












          (void) and () don't mean the same thing, and some versions of MSVC will barf when it comes across () in a function declaration. Overall I was hoping to have more feedback about the malloc algorithm and the implementation. I feel like this answer gives more about the syntax usage then anything about the actual implementation.
          – tay10r
          Dec 19 at 16:05




          (void) and () don't mean the same thing, and some versions of MSVC will barf when it comes across () in a function declaration. Overall I was hoping to have more feedback about the malloc algorithm and the implementation. I feel like this answer gives more about the syntax usage then anything about the actual implementation.
          – tay10r
          Dec 19 at 16:05




          1




          1




          I'm not saying your usage is invalid or conjured. I'm suggesting ways to make your code more concise. You made a generic request for anything that 'stands out'; the above stand out to me. If I find more time I'll review your algorithm.
          – Reinderien
          Dec 19 at 17:43




          I'm not saying your usage is invalid or conjured. I'm suggesting ways to make your code more concise. You made a generic request for anything that 'stands out'; the above stand out to me. If I find more time I'll review your algorithm.
          – Reinderien
          Dec 19 at 17:43




          2




          2




          @tay10r (void) and () in a function declaration do not mean the same thing. Yet in a function definition, they do.
          – chux
          Dec 19 at 22:02




          @tay10r (void) and () in a function declaration do not mean the same thing. Yet in a function definition, they do.
          – chux
          Dec 19 at 22:02




          1




          1




          @Reinderien () in a function declaration has meant "no info about the arguments" which does allow calling the function with any number of arguments - rightly or wrongly - without warning. A C89 throwback.
          – chux
          Dec 19 at 22:12




          @Reinderien () in a function declaration has meant "no info about the arguments" which does allow calling the function with any number of arguments - rightly or wrongly - without warning. A C89 throwback.
          – chux
          Dec 19 at 22:12


















          draft saved

          draft discarded




















































          Thanks for contributing an answer to Code Review Stack Exchange!


          • Please be sure to answer the question. Provide details and share your research!

          But avoid



          • Asking for help, clarification, or responding to other answers.

          • Making statements based on opinion; back them up with references or personal experience.


          Use MathJax to format equations. MathJax reference.


          To learn more, see our tips on writing great answers.





          Some of your past answers have not been well-received, and you're in danger of being blocked from answering.


          Please pay close attention to the following guidance:


          • Please be sure to answer the question. Provide details and share your research!

          But avoid



          • Asking for help, clarification, or responding to other answers.

          • Making statements based on opinion; back them up with references or personal experience.


          To learn more, see our tips on writing great answers.




          draft saved


          draft discarded














          StackExchange.ready(
          function () {
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f209981%2fsimple-malloc-implementation%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown





















































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown

































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown







          Popular posts from this blog

          Список кардиналов, возведённых папой римским Каликстом III

          Deduzione

          Mysql.sock missing - “Can't connect to local MySQL server through socket”