C语言哈希表实现中lookup_size函数触发Seg Fault问题排查
问题背景
实现动态内存分配器作业,需要自主实现m61_malloc和m61_free函数,要求每次分配释放都更新统计结构体里的active_size(当前活跃分配总大小),因此配套实现了一个哈希表,存储分配指针和对应块大小,free时通过lookup_size查询指针对应大小来更新统计值。目前运行时在lookup_size的return tmp->payload;行触发段错误。
完整实现代码如下:
#define M61_DISABLE 1 #include "m61.hh" #include <cstdlib> #include <cstring> #include <cstdio> #include <cinttypes> #include <cassert> #define TABLE_SIZE 10 static m61_statistics stat_count = {0, 0, 0, 0, 0, 0, 0, 4294967295}; typedef struct ht_item { void* address; unsigned long long payload; struct ht_item* next; } ht_item; ht_item* hash_table[TABLE_SIZE]; int hash_func(void* address) { uintptr_t hash_value = (uintptr_t) address % TABLE_SIZE; return hash_value; } void init_hash_table() { for (int i = 0; i < TABLE_SIZE; i++) { hash_table[i] = NULL; } } bool insert_item(ht_item* item) { if (item == NULL) { return false; } int index = hash_func(item->address); item->next = hash_table[index]; hash_table[index] = item; return true; } 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; 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); } void m61_free(void* ptr, const char* file, long line) { (void) file, (void) line; if (ptr){ --stat_count.nactive; stat_count.active_size -= lookup_size(ptr); } base_free(ptr); }
故障原因
段错误的直接原因是遍历哈希链表到末尾tmp == NULL时,仍然访问tmp->payload,根本原因是代码存在多个逻辑错误,导致传入lookup_size的指针根本不在哈希表中:
m61_malloc重复调用底层分配函数,存入哈希表的指针和返回给用户的指针不是同一个
代码里先后三次调用base_malloc(sz):第一次用来判断分配是否成功,第二次的结果存入哈希表,第三次的结果返回给调用方。用户拿到的是第三次分配的指针,free时传入这个指针,哈希表里根本没有对应记录,遍历到最后tmp为NULL,触发段错误。- 每次malloc都重置哈希表
init_hash_table()被放在malloc成功分支里,每次新分配都会把之前所有的哈希记录全部清空,之前分配的指针记录全部丢失,free时自然查不到。这个初始化函数全局只需要执行一次。 - 哈希节点分配函数用错
分配哈希表节点时调用了malloc而非base_malloc,要么会递归调用自己实现的m61_malloc导致死循环,要么会和底层分配逻辑冲突。 - free时未删除哈希表项
内存释放后没有删除哈希表中对应的记录,会造成内存泄漏,后续如果新分配的内存复用同一地址,还会查询到错误的大小值。
修复方案
- 把哈希表初始化逻辑移到全局初始化流程,保证只执行一次:
// 程序启动时自动执行,初始化哈希表 __attribute__((constructor)) void global_init() { init_hash_table(); }
- 重写
m61_malloc逻辑,只调用一次base_malloc,用base_malloc分配哈希节点:
void* m61_malloc(size_t sz, const char* file, long line) { (void) file, (void) line; void* ret = base_malloc(sz); if (!ret) { stat_count.nfail++; stat_count.fail_size += sz; return NULL; } // 更新统计值 stat_count.nactive++; stat_count.ntotal++; stat_count.active_size += sz; stat_count.total_size += sz; // 分配哈希节点,存入记录 ht_item* node = (ht_item*)base_malloc(sizeof(ht_item)); node->address = ret; node->payload = sz; node->next = NULL; insert_item(node); return ret; }
- 修改
lookup_size,增加查找失败的判断逻辑:
unsigned long long lookup_size(void* p) { if (!p) return 0; int idx = hash_func(p); ht_item* tmp = hash_table[idx]; while (tmp) { if (tmp->address == p) { return tmp->payload; } tmp = tmp->next; } // 查找失败返回特殊标记值,对应非法指针free场景 return (unsigned long long)-1; }
- 重写
m61_free逻辑,增加非法指针判断,释放后删除哈希节点:
void m61_free(void* ptr, const char* file, long line) { (void) file, (void) line; if (!ptr) return; unsigned long long sz = lookup_size(ptr); if (sz == (unsigned long long)-1) { // 这里可以按作业要求打印"invalid free"类错误信息 return; } // 更新统计值 stat_count.nactive--; stat_count.active_size -= sz; // 删除哈希表对应节点 int idx = hash_func(ptr); ht_item* tmp = hash_table[idx]; ht_item* prev = NULL; while (tmp) { if (tmp->address == ptr) { if (prev) { prev->next = tmp->next; } else { hash_table[idx] = tmp->next; } base_free(tmp); break; } prev = tmp; tmp = tmp->next; } // 释放用户内存 base_free(ptr); }
内容的提问来源于stack exchange,提问作者azimr24
相关产品推荐
相关产品推荐

