代码非常简单:
#include <vector>
int main() {
std::vector<int> v;
}
然后我在Linux上使用Valgrind构建并运行它:
g++ test.cc && valgrind ./a.out
==8511== Memcheck, a memory error detector
...
==8511== HEAP SUMMARY:
==8511== in use at exit: 72,704 bytes in 1 blocks
==8511== total heap usage: 1 allocs, 0 frees, 72,704 bytes allocated
==8511==
==8511== LEAK SUMMARY:
==8511== definitely lost: 0 bytes in 0 blocks
==8511== indirectly lost: 0 bytes in 0 blocks
==8511== possibly lost: 0 bytes in 0 blocks
==8511== still reachable: 72,704 bytes in 1 blocks
==8511== suppressed: 0 bytes in 0 blocks
...
==8511== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
这里没有内存泄漏,即使有1个alloc和0个free。这个question的答案引用了Valgrind的FAQ中的这一段作为解释 -
C ++标准库的许多实现都使用自己的内存池分配器。许多被破坏对象的内存不会立即被释放并返回给操作系统,而是保存在池中以供以后重复使用。
我的主要问题是:
C ++库实现如何实现?它是否在后台处理一个单独的进程来处理来自其标准模板的所有分配请求,以便当程序退出时(a.out
此处),内存不会立即返回给操作系统?如果是这样,它什么时候会回馈,如何检查过程确实存在?如果不是,幕后背后的“神奇”是什么?
另一个问题:
分配了71 KB。为什么这个号码?
感谢:)
答案 0 :(得分:5)
首先,您没有使用未使用的vector
测试任何内容。编译器很聪明,gcc
clang
和-O2
都将上面的代码编译为空main()
(除了xor eax, eax
以外设置返回值查看程序集here。此外,大多数vector
实现(包括gcc
和clang
)的默认构造函数甚至不会分配任何内容 - 它将等到第一个在进行昂贵的分配步骤之前添加了元素。
要获得更具体的结果,请分配一个BIG向量(以便将其与噪声区分开来)并将其传递给另一个转换单元中的方法(或在单独的.cpp文件中定义),如下所示:
#include <vector>
void sink(std::vector<int>& v);
int main() {
std::vector<int> v(12345678);
sink(v);
}
现在,当您检查程序集时,您会看到它是actually doing something。
因此,Valgrind报告的~72,000字节与您的std::vector<int> v
无关,您可能会看到完全为空的主要内容的相同数字。
问题和引用文献的想法仍然与该问题不同,我将在下面回答。
所有内存通常会在程序退出时释放回操作系统,并且操作系统会强制执行此操作,而不是标准库。操作系统只是清理进程使用的所有资源,包括非共享内存分配。当Valgrind引用“在退出时使用”时,它正在讨论此操作系统清理之前,因为这是你想要知道的,看你是否忘记了任何东西。
您无需任何单独的流程来处理此问题。它是通过让Valgrind跟踪malloc
和free
调用以及其他一些标准分配例程来实现的。
您在常见问题解答中引用的关于使用“使用自己的内存池分配器”的许多标准库的注释是指标准库可以在调用其中一个已知分配调用的那些上使用另一个缓存分配层的想法例如,当需要内存时,malloc
或operator new
最初,但是当内存被解除分配时,它会在内部将其保存在某个列表中,而不是调用相应的取消分配例程(例如free
或delete
)。
在后续分配中,它将使用其内部列表中的内容而不是返回标准方法(如果列表已用完,则必须调用标准例程)。这将使Valgrind看不到它,它会认为应用程序仍在“使用”内存。
由于旧版C ++中std::allocator
内容的定义有些无用,因此没有大量使用,我不同意“很多”标准库默认使用这种类型的池分配器 - 至少在今天:我实际上并不知道任何在主要的标准库实现之间再做这个,尽管有些人在过去做过。但是,allocator参数是每个容器类的模板参数,因此最终用户也可以执行此自定义,尤其是因为allocator
接口已在新标准中得到改进。
这种池化分配器在实践中的大胜利是(a)对容器使用线程本地的固定大小分配,因为所有包含的对象大小相同,并且(b)允许分配器在容器中释放所有内容被摧毁而不是逐个元素释放。
你引用的文档有点令人困惑,因为它谈到(不)将内存重新调整到 OS - 但它应该说“重新调整到标准分配例程”。 Valgrind不需要将内存返回到操作系统以将其视为已释放 - 它会挂钩所有标准例程并知道何时释放该级别。如上所述,标准例程本身大量“缓存”已分配的内存(这是常见的,不像分配器例程缓存这种情况不常见),因此如果Valgrind需要将内存返回到OS,那么报告“退出时分配的内存”将毫无用处。
答案 1 :(得分:4)
我认为你误解了这一点。如果应用程序终止,则将内存返回给操作系统。但是内存没有返回给os,只是因为对象被破坏了
答案 2 :(得分:1)
C ++库实现是如何实现的?
不是。 valgrind信息已经过时,我认为没有任何现代C ++实现能够做到这一点。
它是否在后台处理来自其标准模板的所有分配请求的单独进程,以便在程序退出(此处为a.out)时,不会立即将内存分配给操作系统?
不,您误会了。 valgrind文档并不是在谈论保持内存的寿命超过流程。它只是在谈论将内存池保留在进程内,以便由进程分配和再分配的内存被保存在一个池中,并在以后重用(由同一进程!),而不是立即调用free
。但是,如今std::allocator
并没有这样做,因为std::allocator
必须是通用的,并且在所有情况下都能表现良好,并且无论如何,良好的malloc
实现应该可以满足这些需求。用户使用tcmalloc或jemalloc之类的替代方案覆盖默认系统malloc也很容易,因此,如果std::allocator
仅转发到malloc,那么它将获得替换malloc的所有好处。
如果是,它将何时退还?我如何检查该过程是否确实存在?如果没有,那么幕后的“魔力”是什么?
当进程退出时,进程中的所有内存将返回到OS。没有魔术。
但是您所看到的分配与此无关。
已分配71 KB。为什么会有这个数字?
您所看到的72kb是由C ++运行时为其“紧急异常处理池”分配的。该池用于分配异常对象(例如bad_alloc
异常),即使malloc
不能再分配任何东西。我们在启动时进行了预分配,因此如果malloc
内存不足,我们仍然可以抛出bad_alloc
异常。
具体数字来自此代码:
// Allocate the arena - we could add a GLIBCXX_EH_ARENA_SIZE environment
// to make this tunable.
arena_size = (EMERGENCY_OBJ_SIZE * EMERGENCY_OBJ_COUNT
+ EMERGENCY_OBJ_COUNT * sizeof (__cxa_dependent_exception));
arena = (char *)malloc (arena_size);
较新版本的valgrind
知道此紧急EH池,并在进程退出之前立即调用一个特殊函数将其释放,以使您看不到in use at exit: 72,704 bytes in 1 blocks
。这样做是因为太多的人不了解仍在使用(并且仍然可以访问)的内存并不是泄漏,并且人们一直抱怨它。因此,现在valgrind释放了它,只是为了阻止人们抱怨。当不在valgrind下运行时,不会释放该池,因为这样做是不必要的(无论如何,该进程退出时,操作系统都会对其进行回收)。