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

调用free()后仍存在内存泄漏?多线程C程序问题排查求助

多线程C程序内存泄漏分析与修复

问题概述

测试的多线程C程序存在两处固定内存泄漏,无论是否调用free()均会出现,使用leaks工具检测到的泄漏报告如下:

leaks Report Version: 4.0, multi-line stacks
Process 21334: 226 nodes malloced for 18 KB
Process 21334: 2 leaks for 544 total leaked bytes.

STACK OF 1 INSTANCE OF 'ROOT LEAK: <malloc in tokenizer>':
3   libsystem_pthread.dylib               0x18d51eda0 thread_start + 8
2   libsystem_pthread.dylib               0x18d523fa8 _pthread_start + 148
1   a.out                                 0x102c13c10 tokenizer + 36
0   libsystem_malloc.dylib                0x18d364d88 _malloc_zone_malloc_instrumented_or_legacy + 128 
====
    1 (272 bytes) ROOT LEAK: <malloc in tokenizer 0x120004080> [272]

STACK OF 1 INSTANCE OF 'ROOT LEAK: <calloc in printwords>':
3   libsystem_pthread.dylib               0x18d51eda0 thread_start + 8
2   libsystem_pthread.dylib               0x18d523fa8 _pthread_start + 148
1   a.out                                 0x102c13d4c printwords + 40
0   libsystem_malloc.dylib                0x18d364eb0 _malloc_zone_calloc_instrumented_or_legacy + 92 
====
    1 (272 bytes) ROOT LEAK: <calloc in printwords 0x11ef041d0> [272]

程序功能为读取文本文件、拆分单词并打印,即使跳过打印操作,泄漏情况一致。

泄漏原因分析

1. tokenizer函数初始malloc泄漏

tokenizer函数开头执行char* bfr = malloc(sizeof(char)*BUFSIZE);分配内存,但后续pop_head(deque_12, (void*)&bfr,1);会将bfr指针覆盖为队列中存储的line地址,初始malloc的内存地址永久丢失。后续free(bfr);释放的是最后一次pop_head获取的地址,而非初始分配的内存,导致272字节泄漏。

更严重的是,pop_head的ismallocd=1参数会触发free(tmp->data),即在获取bfr指针后立即释放其指向的内存,后续用strtok_r处理bfr属于访问已释放内存,触发未定义行为。

2. printwords函数初始calloc泄漏

printwords函数开头执行char* word= calloc(1, sizeof(char)*BUFSIZE);分配内存,但pop_head(deque_23, (void*)&word,0);会将word指针覆盖为队列中存储的token地址(该地址指向tokenizer中bfr的内存区域),初始calloc的内存地址丢失。后续free(word);释放的是最后一次pop_head获取的地址,导致272字节泄漏。

此外,token是strtok_r返回的子指针,指向bfr内存,当tokenizer释放bfr后,deque_23中的token全部变为悬空指针,printwords访问这些指针属于非法内存操作。

修复方案

步骤1:修复初始指针泄漏

修改tokenizer函数

删除初始malloc,将bfr初始化为NULL,调整pop_head的ismallocd参数为0,由函数自行释放bfr;同时复制token到新内存,避免悬空指针:

void* tokenizer(){
   struct timespec t = {0,1000000};
   while(isEmpty(deque_12))
      nanosleep(&t, NULL);
   char* bfr = NULL; // 移除初始malloc,改为NULL初始化
   while(1){
      pop_head(deque_12, (void*)&bfr,0); // ismallocd改为0,不在pop_head中释放data
      if(bfr==NULL && rd) break;
      else if(bfr==NULL){
         nanosleep(&t, NULL);
         continue;
      }
      char* state;
      char* token = strtok_r(bfr, " ",&state);
      while(token!=NULL){
          // 复制token到新内存,避免悬空指针
          char* token_copy = malloc(strlen(token)+1);
          strcpy(token_copy, token);
          push_tail(deque_23, (void*)token_copy);
          token = strtok_r(NULL, " ",&state);
      }
      free(bfr); // 处理完后手动释放bfr
   }
   tk=1;
   puts("2: Tokenized");
   return NULL;
}

修改printwords函数

删除初始calloc,将word初始化为NULL,处理完每个word后手动释放内存:

void* printwords(){
   struct timespec t = {0,1000000};
   char* word = NULL; // 移除初始calloc,改为NULL初始化
   while(1){
      pop_head(deque_23, (void*)&word,0);
      if(word==NULL && tk) break;
      else if(word==NULL){
         nanosleep(&t, NULL);
         continue;
      }
      printf(">%s\n",word);
      fflush(stdout);
      free(word); // 处理完每个word后释放内存
   }
   puts("3: all words printed");
   return NULL;
}

步骤2:修复pop_head函数逻辑错误

原pop_head函数在返回数据指针后立即释放内存,导致悬空指针。调整逻辑,移除pop_head内的free(tmp->data),由调用者负责内存释放:

void pop_head(Deque_t* d, void** val, int ismallocd){
   mtx_lock(&d->mtx);
   if(isEmpty(d)){
      mtx_unlock(&d->mtx);
      *val = NULL;
      return;
   }
   Node_t *tmp = d->head;
   *val = d->head->data;
   if(d->head == d->tail){
      d->head = NULL;
      d->tail = NULL;
   }
   else{
      d->head = d->head->next;
      d->head->prev = NULL;
   }
   mtx_unlock(&d->mtx);
   // 移除此处的free(tmp->data),由调用者管理内存
   free(tmp);
}

步骤3:调整main函数的free_deque调用

deque_23中存储的是tokenizer分配的token副本,需设置ismallocd=1来释放这些内存:

int main(int argc, char *argv[]){
   // ... 其他代码不变
   free_deque(deque_12,1);
   free_deque(deque_23,1); // 改为1,释放每个token的副本内存
   free(thpool);
}

验证

修改后重新编译运行,使用leaks工具检测,两处内存泄漏应完全消失,同时程序避免了悬空指针导致的未定义行为。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 03:54:50