Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ std::vector behaves like leaking memory in specific situations

I found a situation where vectors behave like leaking memory and could boil it down to a minimal working example. In this example I make (in a function) a vector which holds three vectors of char. Firstly, these vectors of char get pushed a large number of elements and their capacities are shrinked to their sizes. Then one-element sized vectors are assigned over the large vectors. The problem is now, that the used memory is too large and even when the function returns and the vectors are destroyed, the memory is not freed up. How do I get the memory back? Why does it show this bahaviour? What can I do to avoid this leaking behaviour?

Here the example code (sorry for the length):

#include <iostream>
#include <vector>
#include <string>
#include <fstream>

using namespace std;

// see http://man7.org/linux/man-pages/man5/proc.5.html at /proc/[pid]/status
string meminfo() {
    // memory information is in lines 11 - 20 of /proc/self/status
    ifstream stat_stream("/proc/self/status",ios_base::in);

    // get VmSize from line 12
    string s;
    for ( int linenum = 0; linenum < 12; ++linenum )
        getline(stat_stream,s);
    stat_stream.close();
    return s;
}

void f() {
    vector<vector<char>> mem(3); // with 1,2 memory is fine
    size_t size = 16777215;      // with 16777216 or greater memory is fine
    for ( vector<char>& v : mem ) {
        for ( unsigned int i = 0; i < size; ++i )
            v.push_back(i);
        v.shrink_to_fit();       // without this call memory is fine
    }

    cout << "Allocated vectors with capacities ";
    for ( vector<char>& v : mem )
        cout << v.capacity() << ", ";
    cout << endl << "used memory is now:   " << meminfo() << endl;

    for ( vector<char>& v : mem ) {
        v = vector<char>{1};
        if ( v.size() != v.capacity() )
            cout << "Capacity larger than size." << endl;
    }
    cout << "Shrinked vectors down to capacity 1." << endl
        << "Used memory is now:   " << meminfo() << endl;
}

int main() {
    cout << "At beginning of main: " << meminfo() << endl;
    f();
    cout << "At end of main:       " << meminfo() << endl;
    return 0;
}

And the output on my machine:

At beginning of main: VmSize:      12516 kB
Allocated vectors with capacities 16777215, 16777215, 16777215,
used memory is now:   VmSize:      78060 kB
Shrinked vectors down to capacity 1.
Used memory is now:   VmSize:      61672 kB
At end of main:       VmSize:      61672 kB

However, valgrind does not see a memory leak.

I guess the parameters in the example are system dependent to show the strange bahaviour. I use Linux Mint Debian Edition with a g++ 4.8.2 and a x86_64 kernel. I compile with:

g++ -std=c++11 -O0 -Wall memory.cpp -o memory

and tried also -O3 and no explicit setting for optimization.

Some interesting points are:

  • When I replace the v = vector<char>{1}; by v.clear(); v.shrink_to_fit(); v.push_back(1); the problem stays the same. Replacing the pushing and shrinking for the large vectors by v = vector<char>(16777215); 'solves' the memory problem.
  • 16777215 = 2^24 - 1, so maybe it has to do something with memory borders.
  • Besides, one would expect from the memory the program uses at the beginning of main (12516 kiB) plus the memory of the large vectors, that the program should use in total approximately 3*16777216 B + 12516 kiB = 61668 kiB, which is approximately the memory it uses in the end.

In the real world application I use vectors to collect operations that should be applied to a stiffness matrix of an FEM simulation. Since I want to get to the limits of what is possible with the available memory (also in terms of speed), I need to save the unfreed memory to avoid swapping. Since swapping really happens, I assume the VmSize value is reliable.

like image 538
John Avatar asked Nov 12 '14 19:11

John


People also ask

Can vectors cause memory leaks C++?

std::vector does not cause memory leaks, careless programmers do. You should also include an example that actually exhibits the behavior you are experiencing, including calls to the CRT debug API. There's a good possibility that you're incorrectly interpreting the leaks based on when they are reported.

How does memory leak happen in C++?

Memory leakage occurs in C++ when programmers allocates memory by using new keyword and forgets to deallocate the memory by using delete() function or delete[] operator. One of the most memory leakage occurs in C++ by using wrong delete operator.

Does std::vector allocate memory?

As mentioned above, std::vector is a templated class that represents dynamic arrays. std::vector typically allocates memory on the heap (unless you override this behavior with your own allocator). The std::vector class abstracts memory management, as it grows and shrinks automatically if elements are added or removed.

What does std::vector do?

1) std::vector is a sequence container that encapsulates dynamic size arrays. 2) std::pmr::vector is an alias template that uses a polymorphic allocator. The elements are stored contiguously, which means that elements can be accessed not only through iterators, but also using offsets to regular pointers to elements.


1 Answers

The problem is that you're misunderstand the meaning of 'freeing' memory in a C++ context. When your application frees memory (using shrink_to_fit or deleting objects or whatever), its actually just releasing the memory to the C++ runtime and NOT NECESSARILY releasing it back to the system for other processes to use. The C++ runtime may choose to retain the memory for reuse later in the same process.

Generally, this happens when the memory is fragmented -- the free memory is surrounded (in the VM space of the program) by in-use memory. Only when the freed memory is at the end of the program's memory space will the C++ runtime choose to (or be able to) return it to the system.

Generally this memory retention is not a problem, as it can usually be reused when more memory is requested by the application. The problem you may have is that, because the C++ runtime cannot move around in-use block of memory, you may not be able to reuse free chunks that are too small. There are all kinds of tricks and heuristics that the runtime may use to try to avoid the situation, but they don't always work.

like image 122
Chris Dodd Avatar answered Oct 22 '22 05:10

Chris Dodd