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

C语言包装malloc/free检测内存泄漏时fopen触发段错误求助

内存泄漏检测工具的段错误问题

我尝试包装C标准库的malloc和free函数来检测内存泄漏,通过在函数里加fprintf把内存分配/释放的地址和大小写入日志文件。但用gcc -o mainapp main.c -Wall -Wextra编译运行时,fopen行会触发段错误;把fopen放到malloc和free函数里也一样会崩。换成printf就能正常运行,环境是Ubuntu 22.04、GCC 11.3.0,完整代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#define __USE_GNU
#include <dlfcn.h>

#define TEST_MEM_LEAK 1 // a value of 1 means to join the memory leak detection, and a value of 0 means not to join

#if TEST_MEM_LEAK

typedef void *(*malloc_t)(size_t size);
malloc_t malloc_f = NULL;

typedef void (*free_t)(void *p);
free_t free_f = NULL;

int malloc_flag = 1;    // It is used to prevent repeated recursion and cannot exit because the printf function will call malloc for memory allocation
int free_flag = 1;
const char* logFileName = "/home/hammamiw/Documents/HeapMonitor/allocs.log";
FILE* fp = NULL;

void initCheck()
{
    fp = fopen("/home/hammamiw/Documents/HeapMonitor/allocs.log", "w");
}

void *malloc(size_t size)
{
    if(malloc_flag) { 
        initCheck();  
        malloc_flag = 0;  // Used to prevent printf from causing an error when calling malloc recursively
        void *p = malloc_f(size);
        fprintf(fp, "malloc, %lx, %lu\n", (uintptr_t)p, size);
        //printf("m\n");
        malloc_flag = 1;  // It is used to ensure that the initial value of flag flag is consistent when malloc in this file is called again
        return p;
    } 
    else {
        return malloc_f(size);  // Here, the malloc function in the system library obtained by dlsym is called
    }   
}

void free(void *p) 
{
    initCheck();
    if(free_flag) {
        //initCheck();  
        free_flag = 0;
        fprintf(fp, "F, %lx\n", (uintptr_t)p);
        //printf("f\n");
        free_f(p);
       free_flag = 1;
    } else {
        free_f(p);
    }
}
#endif

int main()
{
#if TEST_MEM_LEAK // the part from if to endif can be divided into function calls
    malloc_f = dlsym(RTLD_NEXT, "malloc");
    if(!malloc_f) {
        printf("load malloc failed: %s\n", dlerror());
        return 1;
    }
    free_f = dlsym(RTLD_NEXT, "free");
    if(!free_f) {
        printf("load free failed: %s\n", dlerror());
        return 1;
    }
#endif
    void *p1 = malloc(10);  //The malloc function in this article will be called first
    void *p2 = malloc(20);
    
    //Here, p2 is not released and there is a memory leak. Judge by checking whether the number of malloc and free times printed is the same
    free(p2);
    free(p1);
    return 0;
}

问题根源

  1. 递归调用+空指针访问:fopen内部会调用malloc,而你重写的malloc又会调用initCheck执行fopen,形成无限递归。更致命的是,main函数里通过dlsym获取系统malloc指针的代码是在第一次malloc调用之后才执行的,此时malloc_f还是NULL,递归调用时访问空指针直接触发段错误。
  2. 初始化时机不对:程序启动阶段,某些库函数可能会提前调用malloc,这时候malloc_f还没被赋值,同样会触发空指针访问。

修复方案

方案1:用系统调用替代标准库IO,提前初始化函数指针

直接用open、write这类系统调用写日志,它们不会依赖C标准库的malloc,同时用__attribute__((constructor))让函数指针初始化在main之前完成,彻底避免空指针问题:

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#define __USE_GNU
#include <dlfcn.h>

#define TEST_MEM_LEAK 1

#if TEST_MEM_LEAK

typedef void *(*malloc_t)(size_t size);
malloc_t malloc_f = NULL;

typedef void (*free_t)(void *p);
free_t free_f = NULL;

int malloc_flag = 1;
int free_flag = 1;
const char* logFileName = "/home/hammamiw/Documents/HeapMonitor/allocs.log";
int log_fd = -1;

// 用系统调用打开日志,不依赖malloc
void initLog() {
    if (log_fd == -1) {
        log_fd = open(logFileName, O_WRONLY | O_CREAT | O_TRUNC, 0644);
    }
}

void *malloc(size_t size)
{
    if(malloc_flag) { 
        initLog();
        malloc_flag = 0;
        void *p = malloc_f(size);
        // 手动格式化字符串,用write输出
        char buf[64];
        int len = snprintf(buf, sizeof(buf), "malloc, %lx, %lu\n", (uintptr_t)p, size);
        write(log_fd, buf, len);
        malloc_flag = 1;
        return p;
    } 
    else {
        return malloc_f(size);
    }   
}

void free(void *p) 
{
    initLog();
    if(free_flag) {
        free_flag = 0;
        char buf[64];
        int len = snprintf(buf, sizeof(buf), "F, %lx\n", (uintptr_t)p);
        write(log_fd, buf, len);
        free_f(p);
        free_flag = 1;
    } else {
        free_f(p);
    }
}

// 在main执行前就获取系统malloc/free指针
__attribute__((constructor))
void initMallocFree() {
    malloc_f = dlsym(RTLD_NEXT, "malloc");
    if(!malloc_f) {
        char err[128];
        snprintf(err, sizeof(err), "load malloc failed: %s\n", dlerror());
        write(STDERR_FILENO, err, strlen(err));
        _exit(1);
    }
    free_f = dlsym(RTLD_NEXT, "free");
    if(!free_f) {
        char err[128];
        snprintf(err, sizeof(err), "load free failed: %s\n", dlerror());
        write(STDERR_FILENO, err, strlen(err));
        _exit(1);
    }
}
#endif

int main()
{
    void *p1 = malloc(10);
    void *p2 = malloc(20);
    
    free(p2);
    free(p1);
    return 0;
}

编译命令

编译时必须链接dl库,否则dlsym无法正常工作:

gcc -o mainapp main.c -Wall -Wextra -ldl

方案2:保留fprintf但严格控制递归和初始化时机

如果一定要用fprintf,需要确保fopen调用时不会触发自己的malloc,并且用pthread_once保证日志只初始化一次,同时在文件操作时暂时关闭递归保护:

不过方案1更可靠,因为系统调用完全绕过了C标准库的内存分配,不会有递归问题。

内容的提问来源于stack exchange,提问作者Wassim Hammami

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 05:05:23