指针的C++容器

C++ Containers of pointers

本文关键字:容器 C++ 指针      更新时间:2023-10-16

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

我试图理解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"初始化?

感谢

Joey

while(true)
{
int myInt = 5;
int * myIntP = &myInt;
myVec.push_back(myIntP);
myVec.pop_back();
}

实际上,这里只有一个int,值为5的myInt。循环将重复使用相同的循环。你把一个指向那个int的指针推到向量中,然后把它移除。没有内存泄漏,因为您没有分配新的int。

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

如果您在堆栈上创建一个指向变量的指针,那么无论指针是什么,当变量超出范围时,它都将被销毁。破坏指针(只要你不调用delete)对变量没有影响。

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

如果您计划在动态分配的变量上使用指针,那么您应该研究智能指针。

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

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

所以,让我们来看看您的每个例子:

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指针时,它是在推送指针,myIntP指向的地址,而不是该地址的内存值。您必须使用call:取消引用指针

myVec.push_back(*myIntP);

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

您混淆并混淆了"销毁"answers"删除"——它们不是一回事,但在C++中是两个不同的概念。

只有指针才能执行删除操作——如果您试图删除非指针,则会出现编译时错误。删除首先销毁指向的对象,然后将其内存返回到堆。

另一方面,破坏可以发生在任何事情上,但大多数情况下只对调用析构函数的类感兴趣。对于任何没有析构函数的类型(如int或任何原始指针类型),销毁都不会起任何作用。虽然你可以手动销毁一个物体,但你几乎从来没有这样做过——当其他事情发生时,它会自动为你发生。例如,当局部变量超出范围时,它将被销毁。

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

我认为您需要深入了解范围变量的有效性示例:

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

在您的最后一个示例中,您在main的开头声明myInt,并且不对myFunc中的值执行任何操作。

不丢失myInt数据是正常的。它将在返回0之后被擦除;