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

为何Valgrind会在该C++实现中报告内存泄漏?

为什么你的std::thread + fork代码会触发Valgrind内存泄漏?

让我们拆解一下问题的核心——你的代码里的嵌套fork操作和std::thread的交互是导致内存泄漏的关键,和exit()本身关系不大。

问题根源分析

当你在第一次fork出的子进程里创建了std::thread后,紧接着又调用了一次fork():

  • 首先,std::thread底层依赖系统线程库(比如pthreads),创建线程时会分配一些内部管理资源(比如线程控制块)。
  • 当你调用fork()时,操作系统会复制当前进程的地址空间,但只会保留当前正在执行fork的线程——其他线程不会被复制到子进程中。
  • 这就导致第二次fork出的子进程里,虽然有uploader这个std::unique_ptr<std::thread>对象,但是它指向的std::thread对应的实际系统线程根本不存在于这个子进程里。
  • 当这个子进程调用exit(0)时,它无法正确清理std::thread的内部资源——因为这些资源对应的线程在子进程里从未启动过,但内存已经被分配了,Valgrind就会把这标记为确定的内存泄漏。

哪怕父进程(第一次fork的子进程)后来join了线程,也无济于事——因为第二次fork的子进程已经带着这个“残缺”的std::thread对象退出了,没机会释放它的资源。

验证结论的小测试

你可以试试把第二次fork的代码注释掉,运行后Valgrind的泄漏报告就会消失——这说明问题确实出在嵌套fork和std::thread的组合上。

修复方案

针对你的代码,有几个可行的修复方向:

  • 避免嵌套fork:如果可以,不要在创建了std::thread的进程里再次fork。把第二次fork的逻辑放到独立的进程中,或者调整任务流程。
  • 在fork前清理线程资源:在调用第二次fork之前,确保uploader已经被join,并且重置指针(比如uploader.reset()),这样子进程就不会继承这个未完成的线程对象。
  • 在子进程中重置线程对象:在第二次fork出的子进程里,第一时间重置uploader指针,让unique_ptr释放对应的std::thread资源,再执行后续逻辑。

比如修改后的代码片段:

int child_id = fork();
if (!child_id){ 
    uploader.reset(); // 子进程不需要这个线程,先释放资源
    // Do something
    exit(0); 
}

你的原始代码

#include <thread> 
#include <unistd.h> 
#include <sys/wait.h> 
#include <memory> 
using namespace std; 

static void upload(const string url, const string path){ 
    int sync_status;
} 

int main(){ 
    bool flag = true; 
    for(int worker_id = 0; worker_id < 1; worker_id++){ 
        int worker_pid = fork(); 
        if (worker_pid == 0){ 
            while (true){ 
                std::unique_ptr<std::thread> uploader(nullptr); 
                if (flag){ 
                    // This block only occurs once inside while loop 
                    flag = false; 
                    string path = "l", url = "k"; 
                    // Valgrind reports the next line 
                    uploader = std::make_unique<std::thread>(upload, url, path); 
                } 
                int child_id = fork(); 
                if (!child_id){ 
                    // Do something 
                    exit(0); 
                } 
                waitpid(child_id, NULL, 0); 
                if(uploader && uploader->joinable()){ 
                    uploader->join(); 
                } 
            } 
            // Do something 
            exit(0); 
        } 
    } 
    return 0; 
}

Valgrind报告

==16424== 80 bytes in 1 blocks are definitely lost in loss record 2 of 2 
==16424== at 0x4C3017F: operator new(unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so) 
==16424== by 0x10AA39: std::unique_ptr<std::thread::_State, std::default_delete<std::thread::_State> > std::thread::_S_make_state<std::thread::_Invoker<std::tuple<void (*)(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >), std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >(std::thread::_Invoker<std::tuple<void (*)(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >), std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > >&&) (thread:197) 
==16424== by 0x10A04A: std::thread::thread<void (&)(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >), std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&>(void (&)(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >), std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&) (thread:126) 
==16424== by 0x109A64: std::_MakeUniq<std::thread>::__single_object std::make_unique<std::thread, void (&)(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >), std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&>(void (&)(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >), std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&) (unique_ptr.h:825) 
==16424== by 0x109514: main (main.cpp:21)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 09:06:56