【问题标题】:C++ Containers of pointers指针的 C++ 容器
【发布时间】:2016-06-09 15:16:40
【问题描述】:

我今天一直在思考一个问题,在谷歌上很难找到答案。

我试图了解 STL 容器在处理指向在堆上和堆栈上分配的对象的指针时的行为。

所以,从对象开始,没有指针......想象我有......

std::vector<int> myVec;
while(true)
{
    int myInt = 5;
    myVec.push_back(myInt);
    myVec.pop_back();
}

我的理解是 pop_back() 方法将确保向量中包含的整数被删除,而不仅仅是从容器中删除。因此,如果它运行并进行了十亿次迭代,我不应该期望泄漏内存。我插入的所有内容都将被删除。内存检查显示了这种行为。

现在考虑我使用指针向量(指向堆上的对象)...

std::vector<int*> myVec;
while(true)
{
    int * myIntP = new int(5);
    myVec.push_back(myIntP);
    myVec.pop_back();
}

在这种情况下,每次调用 pop_back() 时只应删除指针本身,并且底层对象保持未删除,从而导致内存泄漏。因此,经过十亿次迭代后,我使用了相当多的内存,即使我的向量中没有条目。

现在,如果我有一个指针向量(指向堆栈上的对象)怎么办...

std::vector<int*> myVec;
while(true)
{
    int myInt = 5;
    int * myIntP = &myInt;
    myVec.push_back(myIntP);
    myVec.pop_back();
}

这里的指针指向堆栈对象。他们的内存是否在调用 pop_back() 时被释放?内存检查显示此行为没有内存泄漏。使用的少量内存表明它的行为类似于堆栈上的对象。然而这不是我所期望的,因为如果指针已经从另一个函数传递给我,到堆栈变量,即

void myFunc(int * myIntP)
{
    std::vector<int*> myVec;
    myVec.push_back(myIntP);
    myVec.pop_back();
}
int main()
{
    int myInt = 5;                                                                                                                      
    int * myIntP = &myInt;
    myFunc(myIntP);
    std::cout << (*myIntP) << std::endl;
    return 0;
}

然后允许向量释放此内存,将使 myIntP 指向已删除的数据。所以这肯定是不正确的吗?

谁能帮忙解释一下?

还有“指向堆栈上变量的指针”的名称,即没有用“new”初始化?

谢谢

乔伊

【问题讨论】:

  • a vector 仅存储您的结构。如果您的结构是指针,则向量不会检查它是否有效并且不会释放它指向的内存。当你动态分配时,你可能想要使用智能指针,它的析构函数释放分配的内存。
  • 尽管有类型,但向量并不知道也不关心指针指向的内容。当您从向量中删除一个时,它会删除指针占用的内存,但 从不 删除它所指向的东西,无论该对象是在堆栈上还是在堆上。

标签: c++ pointers containers heap-memory stack-memory


【解决方案1】:
while(true)
{
    int myInt = 5;
    int * myIntP = &myInt;
    myVec.push_back(myIntP);
    myVec.pop_back();
}

这里实际上只有一个 int,myInt,值为 5。循环将重用同一个。您将指向该 int 的指针推入向量中,然后将其删除。没有其他事情发生。没有内存泄漏,因为您没有分配新的整数。

STD 容器对指针的作用与对 32/64 位整数的作用没有什么不同。就他们而言,指针只是另一个数字。因此,如果您将指针插入容器,您有责任将其删除。

【讨论】:

    【解决方案2】:

    如果您在堆栈上创建一个指向变量的指针,则该变量将在超出范围时被破坏,而不管指针如何。并且销毁指针(只要你不对它调用delete)不会对变量产生影响。

    所以如果你之前停止使用你的指针,没问题,如果你存储的时间更长,问题...

    如果你打算在动态分配的变量上使用指针,你应该研究智能指针。

    【讨论】:

    • 因此,如果您在 [对象超出范围] 之前停止使用指针,没问题,如果您将其存储更长时间,问题...我明白这一点,但如果我希望我的 while 循环无限期执行,但不希望在内存中加载大量冗余对象(即,当我调用 pop_back() 时,我知道我已经完成了它们)。我怎样才能确保这一点?
    • 要么不使用指针,要么使用智能指针,以确保在销毁指针时删除分配的内存。
    【解决方案3】:

    这里的指针指向堆栈对象。他们的内存是否在调用 pop_back() 时被释放?

    不,他们不是。当它们超出范围时,它们会被释放,这发生在}。在} 之后,内存不再用于此变量(堆栈帧弹出)并将被重用!因此,如果您没有在推送后立即弹出指针,那么当变量超出范围时,您的向量将包含一个悬空指针。

    【讨论】:

    • 好的,但是如果我希望这个 while 循环永远执行,堆栈对象将永远不会超出范围。但是我仍然想将它们从内存中删除。我自己该如何处理?
    • 堆栈帧超出范围。当我说它超出} 的范围时,这不仅意味着while 循环结束时而是在每次 迭代之后。
    • 啊,好吧。那是我不明白的。那么,我让对象在它们的指针被弹出后占用内存的唯一原因是,如果我用“new”声明了指针并且没有调用“delete”?
    • 是的,完全正确。 Stackspace(局部变量,本质上是所有不是用new 创建的)在创建它们的范围结束时自动释放。
    【解决方案4】:

    那么,让我们来看看你的每个例子:

    std::vector<int> myVec;
    while(true)
    {
        int myInt = 5;
        myVec.push_back(myInt);
        myVec.pop_back();
    }
    

    push_back() 方法制作一个参数的副本 并在内部存储该副本。因此,如果您存储的是堆栈分配的对象而不是原始对象,则会调用复制构造函数。 pop_back() 方法也不做任何假设。它会删除您存储的项目的副本(无论是值还是指针)并将其从其内部存储中删除。如果存储的副本是堆栈分配的对象,则在容器管理其内部内存时将调用类的析构函数,因为副本项将不再在范围内。

    你的第二个例子:

    std::vector<int*> myVec;
    while(true)
    {
        int * myIntP = new int(5);
        myVec.push_back(myIntP);
        myVec.pop_back();
    }
    

    正如您所说,整数是在堆上分配的。调用 push_back() 仍然存储参数。在这种情况下,您没有存储整数“5”的值、指针的值、包含“5”值的内存位置的地址。由于您分配了存储“5”的内存,因此您负责获取该指针并释放内存。 pop_back() 方法不会为您删除指针,也不会返回指针的副本。

    你的第三个例子有细微的差别:

    std::vector<int*> myVec;
    while(true)
    {
        int myInt = 5;
        int * myIntP = &myInt;
        myVec.push_back(myIntP);
        myVec.pop_back();
    }
    

    在这种情况下,您没有在堆上分配任何内存。您将 myInt 的地址(堆栈分配的值)分配给指针。堆栈内存在进程的整个生命周期中都存在,并且不会自行释放。但是,一旦您离开当前范围(while 循环),内存就会被其他东西重用。内存仍然存在,但它可能不再具有您期望的值。

    你的最后一个例子:

    void myFunc(int * myIntP)
    {
        std::vector<int*> myVec;
        myVec.push_back(myIntP);
        myVec.pop_back();
    }
    int main()
    {
        int myInt = 5;                                                                                                                      
        int * myIntP = &myInt;
        myFunc(myIntP);
        std::cout << (*myIntP) << std::endl;
        return 0;
    }
    

    您应该在调用 myFunc() 后释放 myInt 的内存。但是,容器方法不会修改提供的值。他们复制它们。当 myFunc() 推送 myIntP 指针时,它推送的是 pointer,myIntP 指向的地址,而不是该地址在内存中的值。您将不得不取消引用指针,使用调用:

    myVec.push_back(*myIntP);
    

    请注意,即使您这样做了,容器也会复制该值。所以,myInt 仍然不受影响。

    【讨论】:

      【解决方案5】:

      您混淆并混淆了“破坏”和“删除”——它们不是相同的东西,而是 C++ 中的两个不同概念。

      删除只能发生在指针上——如果你试图删除一个非指针,你会得到一个编译时错误。删除首先销毁指向的对象,然后将其内存返回到堆中。

      另一方面,破坏可以发生在任何事情上,但主要只对调用析构函数的类感兴趣。对于没有析构函数的任何类型(例如int 或任何原始指针类型),析构什么都不做。虽然您可以手动销毁一个对象,但您几乎永远不会这样做——当其他事情发生时,它会自动为您发生。例如,当局部变量超出范围时,它会被销毁。

      那么在您上面的代码中,会发生什么?那么你有一个本地的std::vector,当它超出范围时会被销毁。它的析构函数将删除它在内部分配的任何内容,并销毁向量的所有元素。但是,它不会删除向量的任何元素。当你有vector&lt;int&gt; 时,这就是全部,因为没有分配其他任何东西,但是当你有vector&lt;int *&gt; 时,如果分配了这些指针,它们就会泄漏。如果它们没有被分配(如果它们指向本地),就没有什么可以泄漏的。

      【讨论】:

        【解决方案6】:

        我认为你需要深入学习范围变量有效性 例子:

         {
          int myVar;//Construct myVar on the stack
         }//At this point, myVar is deleted with the stack
        

        在您的上一个示例中,您在 main 开头声明 myInt 并且不对 myFunc 中的值做任何事情。

        不丢失 myInt 数据是正常的。返回0后会被擦除;

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-07-09
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多