自定义malloc/free实现:如何释放堆内存并触发已释放内存访问的SEGMENTATION FAULT
实现自定义free函数及内存访问保护
首先先修正你现有icf_malloc的几个问题,这会直接影响后续free的实现:
- 返回值类型错误:
icf_malloc应该返回void*而非size_t,否则调用者无法正确使用内存地址 - 空闲块匹配时的返回错误:你直接返回了块头指针
current,但用户需要的是实际可用内存的起始地址current->data - 块大小记录错误:
new->size应该记录用户请求的size,而非size + sizeof(block_on_free),元数据的大小是固定的,无需计入用户内存尺寸
修正后的icf_malloc代码:
#define _DEFAULT_SOURCE #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/mman.h> // 用于内存保护的mprotect函数 typedef struct block_on_free { size_t size; // 改用size_t更符合内存尺寸的标准类型 struct block_on_free *previous; struct block_on_free *next; } block_on_free; block_on_free *phead = NULL; void* icf_malloc (size_t size) { if (size == 0) return NULL; // 忽略0大小的请求 // 遍历空闲链表,采用首次适配算法寻找合适块 for (block_on_free *current = phead; current != NULL; current = current->next) { if (current->size >= size) { // 从空闲链表移除该块 if (current->previous == NULL) phead = current->next; else current->previous->next = current->next; if (current->next != NULL) current->next->previous = current->previous; // 返回用户可用的内存地址(块头之后的位置) return (void*)((char*)current + sizeof(block_on_free)); } } // 无合适空闲块,向内核申请新内存 size_t total_size = size + sizeof(block_on_free); // 按内存页对齐,为后续mprotect操作做准备 const size_t page_size = sysconf(_SC_PAGESIZE); total_size = (total_size + page_size - 1) & ~(page_size - 1); block_on_free *new_block = sbrk(total_size); if (new_block == (void*)-1) { perror("sbrk failed"); return NULL; // 内存申请失败 } new_block->size = size; new_block->previous = NULL; new_block->next = NULL; return (void*)((char*)new_block + sizeof(block_on_free)); }
实现icf_free函数
1. 核心释放逻辑
free的核心是将已分配块重新加入空闲链表,步骤如下:
- 从用户传入的指针反向计算块头地址(用户拿到的是数据区指针,块头在其前方
sizeof(block_on_free)字节处) - (可选)添加合法性检查,比如通过魔术字验证指针是否属于当前分配器
- 将块插入空闲链表(此处插在头部,也可按地址排序方便后续合并)
- (可选)合并相邻空闲块,减少内存碎片
2. 实现已释放内存访问触发段错误
标准库free不会立即触发段错误,通常是内存被重用或覆盖后才出现问题。要实现立即触发段错误,可以用mprotect将已释放的内存页设置为不可访问权限(PROT_NONE),但需注意mprotect只能操作整页内存,因此申请时必须按页对齐。
完整的icf_free实现:
void icf_free(void* ptr) { if (ptr == NULL) return; // 忽略NULL指针的free请求 // 计算块头地址 block_on_free* block = (block_on_free*)((char*)ptr - sizeof(block_on_free)); // 可选:添加魔术字检查,防止非法指针free // 比如在block_on_free中加入int magic = 0xDEADBEEF;,此处检查该值是否匹配 // 将对应内存页设置为不可访问,触发段错误 const size_t page_size = sysconf(_SC_PAGESIZE); void* page_start = (void*)((unsigned long)block & ~(page_size - 1)); if (mprotect(page_start, page_size, PROT_NONE) == -1) { perror("mprotect failed"); return; } // 将块插入空闲链表头部 block->next = phead; if (phead != NULL) { phead->previous = block; } block->previous = NULL; phead = block; // 可选:合并相邻空闲块(此处实现向前合并) if (block->next != NULL) { // 检查当前块与下一块是否地址连续 if ((char*)block + sizeof(block_on_free) + block->size == (char*)block->next) { block->size += sizeof(block_on_free) + block->next->size; block->next = block->next->next; if (block->next != NULL) { block->next->previous = block; } } } }
关键注意事项
- 魔术字检查:建议在
block_on_free中添加魔术数字段,用于验证指针合法性,避免非法指针的free操作 - 内存页对齐:
mprotect必须操作整页内存,因此申请内存时按页对齐是必要条件,否则可能影响其他内存区域 - 性能 trade-off:频繁使用
mprotect会带来性能开销,标准库malloc通常采用内存重用+后期检测的方式处理野指针,而非立即设为不可访问 - sbrk限制:
sbrk只能从堆顶释放内存,若要将内存还给内核,只能释放堆顶的空闲块,否则只能留在空闲链表中等待重用
内容的提问来源于stack exchange,提问作者Isma Jr
相关产品推荐
相关产品推荐

