【问题标题】:What memory is occupied by std::vector if I only give it a pointer to my object?如果我只给它一个指向我的对象的指针,std::vector 会占用什么内存?
【发布时间】:2016-01-22 13:12:41
【问题描述】:

有些东西我不明白,非常感谢您的澄清。我知道有很多关于 std::containers 和未释放内存的问题,但我仍然不了解一个特定的事实。

下面是一个最小程序,它代表了我在生产系统中遇到的问题。在 cmets 中,在 Ubuntu 上等待 std::cin 时会从 /proc/PROC_NUM/status 读取内存消耗。问题也在 cmets 中。

#include <iostream>
#include <stdlib.h>
#include <fstream>
#include <vector>

class MyObject
{
public:
    MyObject(int r=1000)
    : array(new float[r])
    {
        for (int i = 0; i<r;i++)
        {
            array[i] = random();
        }
    }

    ~MyObject()
    {
        delete[] array;
    }

public:
    float* array;
};


int main(int argc,char*argv[])
{
    char a;
    const int count=100;

    std::cout<<"Start after input"<<std::endl;
    std::cin >> a;
    // VmSize:     12704 kB
    {
        std::vector<MyObject*> vec;
        for(int i=0; i<count; i++)
        {
            vec.push_back(new MyObject);
        }

        std::cout<<"Release after input"<<std::endl;
        std::cin >> a;
        // VmSize:     13100 kB, alright, MyObjects fill around 400kB (what I expected)

        for (int i=0; i<count; i++)
        {
            delete vec[i];
            vec[i]=NULL;
        }

        std::cout<<"Run out of scope of vector after input"<<std::endl;
        std::cin >> a;
        // VmSize:     13096 kB, Why are the 400k not freed yet?
    }

    std::cout<<"Shutdown after input"<<std::endl;
    std::cin >> a;
    // VmSize:     12704 kB, Why are now around 400k freed? The only thing that is freed here is the vector of pointers.

    return 0;
}

如果我改为使用 MyObjects 数组,则在删除它后会立即释放内存:

#include <iostream>
#include <stdlib.h>
#include <fstream>
#include <vector>

class MyObject
{
public:
    MyObject(int r=1000)
    : array(new float[r])
    {
        for (int i = 0; i<r;i++)
        {
            array[i] = random();
        }
    }

    ~MyObject()
    {
        delete[] array;
    }

public:
    float* array;
};


int main(int argc,char*argv[])
{
    char a;
    const int count=100;

    std::cout<<"Start after input"<<std::endl;
    std::cin >> a;
    // VmSize:     12700 kB
    {
        MyObject* vec(new MyObject[count]);

        std::cout<<"Release after input"<<std::endl;
        std::cin >> a;
        // VmSize:     13096 kB, alright, around 400k again
        delete[] vec;

        std::cout<<"Run out of scope of vector after input"<<std::endl;
        std::cin >> a;
        // VmSize:     12700 kB, 400k freed again, perfect.
    }

    std::cout<<"Shutdown after input"<<std::endl;
    std::cin >> a;
    // VmSize:     12700 kB, nothing changed, as expected
    return 0;
}

我读到的答案告诉我不能相信操作系统的内存号(目前我在 Linux 上使用了 /prop/PROC_NO/status 的输出)。我可以用什么来监控内存消耗?我在 XCode Instruments 的 Mac 上尝试了同样的方法,我什至没有这个问题。意味着第一种情况下的内存消耗等于第二种情况。

在 Ubuntu 上,我尝试了不同版本的 gcc 和 clang,它们都表现出相同的行为。

在我的生产系统中,有一个 std::map 而不是一个 std::vector,但问题是一样的。

【问题讨论】:

  • 您可以使用具有跟踪/调试支持的自定义分配器来更准确地了解内存消耗情况。但最好只为正确性而设计。使用智能指针和容器,避免使用普通的new

标签: c++ linux memory vector stl


【解决方案1】:

VMSize 与范围语言中范围的进入和离开几乎没有关系(是否知道有些编程环境没有范围?)。

VMSize 反映了操作系统实际必须使用哪个内存来满足程序的内存需求。例如,如果您使用mallocnew[] 或匿名mmap 分配一大块内存,则地址空间只是保留,而不是占用,因此不会显示在 VMSize 中。

此外,大多数运行时库以大块分配内存,然后对象分配是这些大块的切片;在释放一个对象后,只有大块中的空间被标记为空闲,并且可能会被回收用于下一次分配。释放这么大块内存的典型提示是,如果从它分配的所有对象都被释放并且没有其他对象驻留在其中。因此,您的 std::vector 和您手动分配的对象很可能是从同一个大块分配的,并且周围的 std::vector 实例阻止它返回到系统。 但这一切都只是猜测! 细节取决于所使用的 C++ 运行时库。

【讨论】:

  • 我同意你的大块理论,这里唯一让我困惑的是:为什么我可以在第二个示例中跟踪消费?那里的 VMSize 以某种方式很好地反映了消耗。什么也不完全符合该理论,为什么一旦向量被破坏(在我的示例中是离开范围)后的变化会反映在 VMSize 中?
  • @schluchc:根据猜想,在您的第二个示例中,只有您手动管理的 MyObjects 数组占用了大块,删除后大块是空闲的。这与您的第一个示例形成对比,在该示例中,假定的大块仍然被 std::vector&lt;MyObject*&gt; 实例(以及它所拥有的所有现在已死的指针)占用,直到该实例超出范围为止。
  • 我猜你是对的,但是把这个例子推到极限,真的让我怀疑:拿我的第一个代码 sn-p 并将 for 循环计数更改为 1'000'000,然后该程序需要大约 4GB 的 RAM,在程序完成之前不会返回。我不敢相信没有释放一大块 4GB,因为可能有 100Bytes 仍在使用中。
【解决方案2】:

你的预期是正确的!这是因为 GCC 的 STL 分配策略。 GCC 使用启发式方法。例如,您可能需要重新插入对象。因此,在整个向量消失之前,您对 MyObject 的析构函数的调用在内部是空语句!完整的解释可以在这里找到:https://gcc.gnu.org/onlinedocs/libstdc++/manual/memory.html

根据上述链接的说明:要关闭 STL 优化,请设置 GLIBCXX_FORCE_NEW。 我试过这样:

export GLIBCXX_FORCE_NEW=1
./a.out

现在内存立即被释放!

【讨论】:

    【解决方案3】:

    在您的第一个示例中,您在范围内的堆栈上创建了向量。您可能知道 C++ 具有 RAII http://en.cppreference.com/w/cpp/language/raii 来自动进行资源管理,并且当您离开为向量保留的范围内存时,将被释放。这就是您在示例末尾再次看到 VmSize: 12704 的原因!

    如果您创建的向量不在堆栈上,则第一个示例的行为将符合您的预期。

    #include <iostream>
    #include <stdlib.h>
    #include <fstream>
    #include <vector>
    #include "stdlib.h"
    #include "stdio.h"
    #include "string.h"
    
    
    int parseLine(char* line){
        int i = strlen(line);
        while (*line < '0' || *line > '9') line++;
        line[i-3] = '\0';
        i = atoi(line);
        return i;
    }
    
    
    int getValue(){ //Note: this value is in KB!
        FILE* file = fopen("/proc/self/status", "r");
        int result = -1;
        char line[128];
    
    
        while (fgets(line, 128, file) != NULL){
            if (strncmp(line, "VmSize:", 7) == 0){
                result = parseLine(line);
                break;
            }
        }
        fclose(file);
        return result;
    }
    
    class MyObject
    {
    public:
        MyObject(int r=1000)
        : array(new float[r])
        {
            for (int i = 0; i<r;i++)
            {
                array[i] = random();
            }
        }
    
        ~MyObject()
        {
            delete[] array;
        }
    
    public:
        float* array;
    };
    
    
    int main(int argc,char*argv[])
    {
    
        const int count=100;
        // VmSize:     12704 kB
        std::cout << "VmSize: " << getValue() << std::endl;
        {
            std::vector<MyObject*> *vec = new std::vector<MyObject*>();
            for(int i=0; i<count; i++)
            {
                vec->push_back(new MyObject);
            }
    
            // VmSize:     13100 kB, alright, MyObjects fill around 400kB (what I expected)
            std::cout << "VmSize: " << getValue() << std::endl;
    
            for (int i=0; i<count; i++)
            {
                delete (*vec)[i];
                (*vec)[i]=NULL;
            }
    
    //Now we deallocating memory! In case of stack it will be done automatically when we left the scope.
            delete vec;
            std::cout << "VmSize: " << getValue() << std::endl;
            // VmSize:     13096 kB, Why are the 400k not freed yet?
        }
    
        std::cout << "VmSize: " << getValue() << std::endl;
        // VmSize:     12704 kB, Why are now around 400k freed? The only thing that is freed here is the vector of pointers.
    
        return 0;
    }
    

    【讨论】:

    • 我知道我没有删除向量元素,但它们只是指针,绝对不构成400k。 400k 源自 MyObject 中的浮点数消耗的 100*1000*4 字节。所以我的问题是,为什么他们还没有归还。我从未将它们的所有权交给向量。尽管如此,我尝试添加一个清除,但它没有改变任何东西。
    • @schluchc 我对矢量元素有误,我已经更改了答案。
    猜你喜欢
    • 2021-02-17
    • 2022-12-04
    • 1970-01-01
    • 2016-02-19
    • 2013-03-20
    • 1970-01-01
    • 2012-08-17
    • 2020-03-02
    • 2017-01-14
    相关资源
    最近更新 更多