You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

自定义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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 00:05:54