The code is fairly simple:
#include <vector>
int main() {
std::vector<int> v;
}
Then I build and run it with Valgrind on Linux:
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)
Here, there is no memory leak, even though there is 1 alloc and 0 free. The answer to this question quotes this paragraph from Valgrind's FAQ for explanation -
Many implementations of the C++ standard libraries use their own memory pool allocators. Memory for quite a number of destructed objects is not immediately freed and given back to the OS, but kept in the pool(s) for later re-use.
My main question is:
How does the C++ library implementation achieve that? Does it keep around a separate process in the background that handles all allocation requests from its standard templates, so that when the program exits (a.out
here), the memory is not immediately given back to the OS? If so, when will it give back, and how can I check the process indeed exists? If not, what is the "magic" behind the scene?
Another question:
There is 71 KB allocated. Why this number?
Thanks:)
I think you misunderstood this. The memory is given back to the os if the app terminates. But the memory is not given back to the os, just because the object is destroyed
It doesn't. The valgrind information is outdated, I don't think any modern C++ implementations do that.
No, you've misunderstood. The valgrind docs aren't talking about keeping memory around that outlives the process. It's just talking about keeping memory pools within the process so that memory allocated and then deallocated by the process is kept in a pool and reused (by the same process!) later, instead of calling
free
immediately. But nobody does that forstd::allocator
nowadays, becausestd::allocator
needs to be general purpose and perform reasonably well in all scenarios, and a goodmalloc
implementation should meet those needs anyway. It's also fairly easy for users to override the default system malloc with an alternative like tcmalloc or jemalloc, so ifstd::allocator
just forwards to malloc then it gets all the benefits of that replacement malloc.All memory in a process is returned to the OS when the process exits. There is no magic.
But the allocation you're seeing has nothing to do with this anyway.
The 72kb you're seeing is allocated by the C++ runtime for its "emergency exception-handling pool". This pool is used to be able to allocate exception objects (such as
bad_alloc
exceptions) even whenmalloc
can no longer allocate anything. We pre-allocate at startup, so ifmalloc
runs out of memory we can still throwbad_alloc
exceptions.The specific number comes from this code:
See https://gcc.gnu.org/git/?p=gcc.git;a=blob;f=libstdc%2B%2B-v3/libsupc%2B%2B/eh_alloc.cc;h=005c28dbb1146c28715ac69f013ae41e3492f992;hb=HEAD#l117
Newer versions of
valgrind
know about this emergency EH pool and call a special function to free it right before the process exits, so that you don't seein use at exit: 72,704 bytes in 1 blocks
. This was done because too many people fail to understand that memory still in use (and still reachable) is not a leak, and people kept complaining about it. So now valgrind frees it, just to stop people complaining. When not running under valgrind the pool doesn't get freed, because doing so is unnecessary (the OS will reclaim it when the process exits anyway).First, you aren't testing anything with that unused
vector
. Compilers are smart, and bothgcc
andclang
at-O2
compile the code above to an emptymain()
(other than a singlexor eax, eax
to set the return value. See the assembly here. Also, the default constructor for mostvector
implementations (includinggcc
andclang
) won't even allocate anything - it will wait until the first element is added before taking the expensive step of allocation.To get a more concrete result, allocate a BIG vector (so you can distinguish it from the noise) and pass it to a method in another translation unit (or defined in a separate .cpp file), like this:
Now when you check the assembly, you see it is actually doing something.
So the ~72,000 bytes you are seeing reported by Valgrind has nothing to do with your
std::vector<int> v
and you'd probably see the same figure with a completely empty main.Still the idea of the question and the quoted documentation stands apart from that issue and I'll answer it below.
All memory is generally freed back to the OS when the program exits, and it is the OS that enforces this, not the standard library. The OS simply cleans up all resources used by the process, including an unshared memory allocation. When Valgrind refers to "in use at exit" it is talking about before this OS cleanup occurs, since that's what you want to know to see if you are forgetting to free anything.
You don't need any separate process to handle this. It is implemented by having Valgrind track
malloc
andfree
calls, and perhaps some other standard allocation routines.The comment you quoted from the FAQ about many standard library using "use their own memory pool allocators" is referring to the idea that a standard library may use another caching allocation layer on top of those which calls one of the known allocations calls like
malloc
oroperator new
initially when memory is needed, but when the memory is de-allocated it saves it internally in some list rather than calling the corresponding de-allocation routine (such asfree
ordelete
).On subsequent allocations it will use the stuff in its internal lists in preference to going back to the standard methods (if the list is exhausted, it has to call the standard routines). This would make it invisible to Valgrind, which would consider the memory still "in use" by the application.
Because of the somewhat useless definitions of the
std::allocator
stuff in old versions of C++ this wasn't heavily used, and I don't agree that "many" standard libraries use this type of pool allocator by default - at least today: I am not in fact aware of any that does this anymore between the major standard library implementations, although some did in the past. However, the allocator argument is a template parameter of each container class, so end users may also perform this customization, especially since theallocator
interface has been improved in newer standards.Big wins in practice for such pooled allocators are (a) using thread-local, fixed size allocations for a container as all contained objects are the same size and (b) allowing the allocator to free everything in one operation when the container is destroyed rather than freeing element by element.
The documentation you quoted is a bit confusing because it talks about (not) retuning memory to the OS - but it should really say "retuning to the standard allocation routines". Valgrind does not need memory to be returned to the OS to see it as freed - it hooks all the standard routines and knows when you have freed at that level. The standard routines themselves heavily "cache" allocated memory as described above (this is common, unlike allocator routine caching which is uncommon) so if Valgrind required memory to be returned to the OS it would be quite useless at reporting "allocated memory at exit".