C++ Valgrind未检测到释放内存的危险
我正在学习valgrind框架,我决定在自己的小测试用例上运行它。下面是强制从堆中删除额外对象的程序(我在AMD64/LINUX上运行它):C++ Valgrind未检测到释放内存的危险,c++,debugging,memory,memory-management,valgrind,C++,Debugging,Memory,Memory Management,Valgrind,我正在学习valgrind框架,我决定在自己的小测试用例上运行它。下面是强制从堆中删除额外对象的程序(我在AMD64/LINUX上运行它): #包括 使用名称空间std; 结构Foo { Foo(){coutValgrind未检测到数组“prefix”更改可能是因为它是内存的有效部分。即使用户代码不应该直接更改它,但它仍然由数组构造函数代码访问和修改,并且valgrind没有提供如此精细的访问检查分离。还要注意,此损坏似乎不会损坏堆,因此释放成功 Valgrid没有检测到对无效对象的析构函数调用
#包括
使用名称空间std;
结构Foo
{
Foo(){coutValgrind未检测到数组“prefix”更改可能是因为它是内存的有效部分。即使用户代码不应该直接更改它,但它仍然由数组构造函数代码访问和修改,并且valgrind没有提供如此精细的访问检查分离。还要注意,此损坏似乎不会损坏堆,因此释放成功
Valgrid没有检测到对无效对象的析构函数调用,可能是因为此调用实际上没有访问无效存储。添加一些类字段将改变这种情况:
struct Foo
{
int i;
Foo(): i(0) { cout << i << "Creation Foo" << endl;}
~Foo(){ cout << i << "Deletion Foo" << endl;}
};
structfoo
{
int i;
Foo():i(0){coutValgrind没有检测到内存问题,因为没有内存问题
让我们一步一步地介绍一下您的程序(这取决于实现,但它基本上适用于gcc和其他主要编译器):
调用新Foo[3]
:
分配了8+3*sizeof(Foo)
字节的内存,我们称之为指针p
。需要8个字节来存储数组中的元素数。当调用delete
时,我们需要这个数字
数组中的对象数保存为p[0]=3
为内存地址p+8
、p+8+sizeof(Foo)
和p+8+2*sizeof(Foo)
调用新操作符Foo()
,即创建3个对象
ar
具有地址p+8
,并指向第一个Foo
-对象
操纵对象数量*(重新解释投射(ar)-2)=4
好的,p[0]
现在是4
。每个人都认为数组中有4
对象(但实际上只有3
)
注意:如果Foo
将有一个微不足道的析构函数(比如int
has),那么情况将有所不同,访问ar-8
将是无效的访问
在这种情况下,编译器优化了析构函数的调用,因为无需执行任何操作。但是无需记住元素的数量-因此p
实际上是ar
,并且在开头没有偏移量/额外的8个字节
这就是为什么大多数编译器的代码实际上是错误的:
int *array=new int[10];
delete array;//should be delete [] array;
工作没有问题:内存管理器不需要知道指针后面有多少内存,不管它是一个int还是多个int,它只是释放内存
呼叫删除[]ar
析构函数的调用次数为p[0]=4次,也适用于arr[0]、arr[1]、arr[2]
和arr[3]
。为arr[3]
调用析构函数是一种未定义的行为,但不会发生任何不好的事情:调用析构函数不会释放内存(在您的情况下甚至不会触及内存)。它只会打印出一些东西,没有任何错误
释放数组内存。实际上p
-指针被释放,而不是ar
,因为内存管理器“只知道”p
-我们可以从ar
计算p
。在地下某个地方调用free(p)
-没有人关心它有多少内存-使用的操作符delete(*void)
没有提供它
没什么,从瓦尔格兰德的观点来看,这是个问题
为了使我的观点更清楚(请参见结果汇编程序):
将导致仅调用析构函数(无内存访问),但不会释放内存-这就是程序中对象arr[0]
、arr[1]
、arr[2]
和arr[3]
call Foo::~Foo()
但是
将导致调用析构函数和运算符delete,这将删除堆上的内存:
call Foo::~Foo()
movq %rbp, %rdi
call operator delete(void*) ; deletes memory, which was used for f
然而,在您的案例中,并没有为每个对象调用操作符delete
,因为内存也不是按位分配的,而是作为整个内存块分配的,即p
如果您要调用delete ar;
而不是delete[]ar;
您可以看到发生了什么:
仅为第一个Foo
-对象调用析构函数
程序将尝试释放指针arr
,而不是指针p
。然而,内存管理器不知道指针ar
(它只知道p
),这是有问题的
正如VTT所指出的,如果析构函数触及对象中的某些内存,您将看到对数组之外内存的无效内存访问
如果析构函数必须释放一些内存(例如,有一个向量作为成员),从而将随机内存内容解释为地址,并调用运算符delete
,以删除这些随机地址,则会出现错误。*(reinterpret_cast(ar)-2)=4;
是一种潜在的严格别名冲突,会导致未定义的行为,并且是发布模式下优化的潜在目标,会抛出整行代码。您是如何编译此代码的?@VTT,我更新了问题。我知道这是一种肮脏的黑客行为-我只是想隐式更改数组中分配对象的计数,而要编译的命令是什么您使用?我添加了inti;
但没有添加任何内容occured@LmTinyToon您需要在析构函数cout处实际访问它,但据我所知,运算符delete[]释放了错误长度的块(长度应小于1foo大小).我想这应该已经导致了错误的结果results@LmTinyToon我们处于一个行为未定义的领域,因此一切都依赖于实现。然而,在delete之后,有一个简单的无大小的C-freefree(void*)
。可能有一个内存管理器,它利用指向内存的大小来加快查找速度,
call Foo::~Foo()
Foo *f=new Foo();
delete f;
call Foo::~Foo()
movq %rbp, %rdi
call operator delete(void*) ; deletes memory, which was used for f