简体   繁体   中英

Seg Fault in C Hash Table implementation

I've been working on this dynamic memory allocator problem set where we must implement malloc and free and I was struggling a lot with the implementation of free. Outside of freeing the appropriate memory, there is a statistics struct that we must update with each call of malloc/free which holds a variable for the size of active allocations, active_size. To accurately update active_size the problem set says we should implement a hash table. I made the hash table to hold malloc pointers and the size of their allocations and I included a lookup_size function which would then be used in the free function to update the active_size. However, it seems the lookup_size functions is flawed and causes a seg fault at this line:

return tmp->payload;

I'll leave below the entire code as well in case anyone catches any other mistakes, but it would be a huge help if someone could figure out the cause of this seg fault. Thanks.

#define M61_DISABLE 1
#include "m61.hh"
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <cinttypes>
#include <cassert>

// Hash table size
#define TABLE_SIZE 10

/// m61_malloc(sz, file, line)
///    Return a pointer to `sz` bytes of newly-allocated dynamic memory.
///    The memory is not initialized. If `sz == 0`, then m61_malloc must
///    return a unique, newly-allocated pointer value. The allocation
///    request was at location `file`:`line`.
static m61_statistics stat_count = {0, 0, 0, 0, 0, 0, 0, 4294967295};

// Hash table item defintion
typedef struct ht_item {
    void* address;
    unsigned long long payload;
    struct ht_item* next;
} ht_item;

// Initialize hash table
ht_item* hash_table[TABLE_SIZE];

// Mod hash function
int hash_func(void* address) {
    uintptr_t hash_value = (uintptr_t) address % TABLE_SIZE;
    return hash_value;
}

// Empty hash table
void init_hash_table() {
    for (int i = 0; i < TABLE_SIZE; i++) {
        hash_table[i] = NULL;
    }
}

// Hash table item insertion function
bool insert_item(ht_item* item) {
    // Check if there actually is an item
    if (item == NULL) {
        return false;
    }

    // Insert item to front of the l_list (assign current val to next and new value to hash table)
    int index = hash_func(item->address);
    item->next = hash_table[index];
    hash_table[index] = item;
    return true;
}

// Hash table functiont that finds allocated sizes by mallocc
unsigned long long lookup_size(void* p) {
    if (p == NULL) {
        return 0;
    }

    int index = hash_func(p); 
    ht_item* tmp = hash_table[index];
    while (tmp != NULL && tmp->address != p) {
        tmp = tmp->next;
    }
    return tmp->payload;
}

void* m61_malloc(size_t sz, const char* file, long line) {
    (void) file, (void) line;   // avoid uninitialized variable warnings
    // Your code here.
    if (!base_malloc(sz)){
        ++stat_count.nfail;
        stat_count.fail_size += sz;
    }

    else {
        ++stat_count.nactive;
        ++stat_count.ntotal;
        stat_count.active_size += sz;
        stat_count.total_size += sz;

        init_hash_table();
        void* p = base_malloc(sz);
        ht_item* malloc_data = (ht_item*) malloc(sizeof(ht_item));
        malloc_data->address = p;
        malloc_data->payload = sz;
        malloc_data->next = NULL;
        insert_item(malloc_data);
    }
    return base_malloc(sz);
}


/// m61_free(ptr, file, line)
///    Free the memory space pointed to by `ptr`, which must have been
///    returned by a previous call to m61_malloc. If `ptr == NULL`,
///    does nothing. The free was called at location `file`:`line`.

void m61_free(void* ptr, const char* file, long line) {
    (void) file, (void) line;   // avoid uninitialized variable warnings
    // Your code here.
    if (ptr){
        --stat_count.nactive;
        stat_count.active_size -= lookup_size(ptr);
    }
    base_free(ptr);
}

Before getting into the answer, let me say this:

Dropping a chunk of memory management code on someone with no way to run it besides reverse-engineering the code from compiler output until it works is not nice.

Please, take a good look at this link before asking another question. https://stackoverflow.com/help/minimal-reproducible-example


So, you have this snippet of code:

while (tmp != NULL && tmp->address != p) {
    tmp = tmp->next;
}
return tmp->payload;

Look at the loop condition:

while (tmp != NULL && tmp->address != p) { ... }

Assuming tmp->address never equals p , it keeps iterating as long as tmp is not NULL . In other words, it stops when tmp is NULL .

Then, in the very next line, you try to access tmp . But tmp is already NULL at that point! So you pretty much do this:

return NULL->payload;

And that's what causes the segmentation fault.

If this loop is meant to always succeed when free is called on a valid pointer, then you're probably not reliably calling insert_item in some case (in m61_malloc )?

Or, if insert_item should not be called on every call to m61_malloc , then you would not expect to find the item on every call to lookup_size , so you should check that tmp is not NULL after the loop. (you should probably do this regardless, because someone might call m61_free with an invalid pointer).


As for the cause of this bug, m61_malloc seems pretty fishy...

if (!base_malloc(sz)) {
    ...
} else {
    ...
    void* p = base_malloc(sz);
    ...
}
return base_malloc(sz);

What do you expect this to do when the first call to base_malloc does not return NULL ? As it stands, it goes into the 'else' branch, calls base_malloc a second time (either allocating again or returning a NULL that isn't handled), then calls base_malloc a third time in the return statement (again, possibly returning NULL ).

In this scenario, the pointer that is stored in the hash table is the one that was returned by the second call to base_malloc , but the pointer that is returned (and that might be passed into m61_free later) is the one returned by the third call to base_malloc (which is NOT stored in the hash table).

Maybe this is the true cause of the error?

EDIT: changing m61_malloc as below fixes this particular segfault, though it's still wrong.

if (!base_malloc(sz)) {
    ...
    return NULL;
} else {
    ...
    void* p = base_malloc(sz);
    ...
    return p;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM