我有一个用 C++ 编写的简单程序,其构建配置如下:
- 使用/链接
libstdc++
- 使用/链接
libc++
我使用valgrind运行这两个版本,如下所示:
valgrind –leak-check=full –show-reachable=yes –track-origins=yes –log-file=test_program.log -v./test_program
该libstdc++
版本运行后没有出现内存泄漏:
==
== HEAP SUMMARY:
== in use at exit: 0 bytes in 0 blocks
== total heap usage: 24,813,106 allocs, 24,813,106 frees, 51,325,970,073 bytes allocated
==
== All heap blocks were freed -- no leaks are possible
==
== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
然而libc++
运行时它显示内存泄漏:
==434036== HEAP SUMMARY:
==434036== in use at exit: 16 bytes in 1 blocks
==434036== total heap usage: 317,709,577 allocs, 317,709,576 frees, 645,827,127,171 bytes allocated
==434036==
==434036== Searching for pointers to 1 not-freed blocks
==434036== Checked 401,408 bytes
==434036==
==434036== 16 bytes in 1 blocks are still reachable in loss record 1 of 1
==434036== at 0x484DA83: calloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==434036== by 0x49A365F: ??? (in /usr/lib/llvm-14/lib/libc++abi.so.1.0)
==434036== by 0x49A24E9: __cxa_get_globals (in /usr/lib/llvm-14/lib/libc++abi.so.1.0)
==434036== by 0x49A53F6: __cxa_throw (in /usr/lib/llvm-14/lib/libc++abi.so.1.0)
==434036== by 0x2EE7B3: goal::details::special_node<double>::value() const (in workspace/goal/goal_test)
==434036== by 0x2DC349: goal::details::caller_node<double>::value() const (in workspace/goal/goal_test)
==434036== by 0x50AC40: double goal::details::arg_node<double>::process<main_node<double> > const&) (in workspace/goal/goal_test)
==434036== by 0x45A79C: bool execute_test_base<double>() (in workspace/goal/goal_test)
==434036== by 0x2322A0: main (in workspace/goal/goal_test)
==434036==
==434036== LEAK SUMMARY:
==434036== definitely lost: 0 bytes in 0 blocks
==434036== indirectly lost: 0 bytes in 0 blocks
==434036== possibly lost: 0 bytes in 0 blocks
==434036== still reachable: 16 bytes in 1 blocks
==434036== suppressed: 0 bytes in 0 blocks
==434036==
==434036== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
我还对该程序进行了另外两个构建:
- 使用/
libstdc++
链接ASAN/LSAN/UBSAN/TSAN
- 使用/
libc++
链接ASAN/LSAN/UBSAN/TSAN
运行它们时,它们都不会触发清理错误或警告。
使用的编译器:
- g++-13 (Ubuntu 13.1.0-8ubuntu1~22.04) 13.1.0
- clang 版本 20.0.0git
仅当与 libc++ 链接时,两个编译器上才会观察到 valgrind 泄漏。
问题: valgrind 的泄漏可能是误报吗?还可以做些什么来验证它是合法的?
12
最佳答案
2
简而言之:您上面描述的情况不是内存泄漏,因为一块内存被分配(例如通过 malloc)给一个指针,后来该指针被覆盖或以其他方式丢失(范围),导致无法显式释放分配的内存。
那么valgrind给出的泄漏报告是什么?
标准库实现在如何实现指定的 C++ 标准功能方面具有很大的自由度。
在您的情况下,您看到的是libc++
,与不同libstdc++
,当可执行文件第一次尝试在给定线程内引发异常时(main 也被视为线程),会创建一个线程本地存储(TLS)内存实例(通过) 。__calloc_with_fallback
(aka process)
__cxa_eh_globals * __cxa_get_globals () {
// Try to get the globals for this thread
__cxa_eh_globals* retVal = __cxa_get_globals_fast ();
// If this is the first time we've been asked for these globals, create them
if ( NULL == retVal ) {
retVal = static_cast<__cxa_eh_globals*>
(__calloc_with_fallback (1, sizeof (__cxa_eh_globals)));
if ( NULL == retVal )
abort_message("cannot allocate __cxa_eh_globals");
if ( 0 != std::__libcpp_tls_set ( key_, retVal ) )
abort_message("std::__libcpp_tls_set failure in __cxa_get_globals()");
}
return retVal;
}
代码可以在这里找到:
实例化内存的代码libc++
立即通过调用将关联指针注册到关闭的内存管理器__libcpp_tls_set
。
这里的想法是,作为进程关闭过程的一部分:一旦进程的所有状态都被释放/销毁/清理,注册的TLS
相关分配(因为进程中可能有多个线程)__libcpp_tls_set
最终会被释放(或释放)。
Valgrind在这里提出了一个问题,因为它无法跟踪分配的真实位置并将其与相关的销毁对齐,尽管自进程运行开始以来分配的所有内存最终都会被明确释放 – 而不是通过操作系统清理隐式释放。
使用以下代码可以轻松复制您所看到的泄漏:
int main()
{
try
{
throw 1;
}
catch (int)
{}
return 0;
}
建造:
c++ -pedantic-errors -Wall -Wextra -Werror -O2 -o exceptiontest exceptiontest.cpp -L/usr/lib -lc++
运行 valgrind:
valgrind –leak-check=full –show-reachable=yes –track-origins=yes –log-file=exceptiontest.log -v./exceptiontest
Valgrind 输出:
==551373== HEAP SUMMARY:
==551373== in use at exit: 16 bytes in 1 blocks
==551373== total heap usage: 2 allocs, 1 frees, 160 bytes allocated
==551373==
==551373== Searching for pointers to 1 not-freed blocks
==551373== Checked 135,200 bytes
==551373==
==551373== 16 bytes in 1 blocks are still reachable in loss record 1 of 1
==551373== at 0x484DA83: calloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==551373== by 0x48AE65F: ??? (in /usr/lib/llvm-14/lib/libc++abi.so.1.0)
==551373== by 0x48AD4E9: __cxa_get_globals (in /usr/lib/llvm-14/lib/libc++abi.so.1.0)
==551373== by 0x48B03F6: __cxa_throw (in /usr/lib/llvm-14/lib/libc++abi.so.1.0)
==551373== by 0x109105: main (in temp/exception_test/exceptiontest)
==551373==
==551373== LEAK SUMMARY:
==551373== definitely lost: 0 bytes in 0 blocks
==551373== indirectly lost: 0 bytes in 0 blocks
==551373== possibly lost: 0 bytes in 0 blocks
==551373== still reachable: 16 bytes in 1 blocks
==551373== suppressed: 0 bytes in 0 blocks
==551373==
==551373== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
如您所见,编写的程序非常简单且完全符合标准,并且不会显式或隐式泄漏内存,但根据所使用的 c++ 标准库实现,valgrind 会引发与未释放块相关的问题。
当使用构建程序时libstdc++
,正如预期的那样,没有产生泄漏或可到达的块:
==551500== HEAP SUMMARY:
==551500== in use at exit: 0 bytes in 0 blocks
==551500== total heap usage: 2 allocs, 2 frees, 73,860 bytes allocated
==551500==
==551500== All heap blocks were freed -- no leaks are possible
==551500==
==551500== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
2
-
我认为这是 libc++ 的缺陷。libstdc++ 有一个 freeres 函数,可以在 Valgrind 下运行时释放这样的内存。
– -
1这绝对是一个极好的答案!它解释了一切,谢谢@Patrick!
–
|
简单的规则。不要自欺欺人地认为可能会有假阳性。这几乎总是一厢情愿的想法和确认偏见。
Valgrind memcheck 确实会产生一些误报,但这种情况很少见。泄漏检测是一项“简单”的工作,因此误报率基本为零。
Memcheck 可以比清理程序更好地检测泄漏,因为它可以完整查看客户机 exe 的执行情况,从第一条指令到最后一条指令。我不是清理程序专家,但我认为清理程序代码只能在第一个全局构造函数执行时启动。
正如评论中所说,如果您的 exe 抛出了以 std::terminate 结尾的异常,那么像 atexit 清理这样的操作就不会执行。只有当 exe 执行干净退出时,泄漏检测才有意义。
2
-
您所说的一切都没有意义,没有调用 std::terminate,运行时两个构建都正常结束而没有失败。
– -
在这种情况下,这是 libc++ 问题。他们应该释放所有内存,或者提供 freeres 函数。
–
|
main
”下报告它。–
–
–
–
–
|