为啥这段代码不会导致内存泄漏? [复制]
Posted
技术标签:
【中文标题】为啥这段代码不会导致内存泄漏? [复制]【英文标题】:Why does this code not result in a memory leak? [duplicate]为什么这段代码不会导致内存泄漏? [复制] 【发布时间】:2013-10-03 09:35:34 【问题描述】:我用valgrind
和--leak-check=full
检查了以下C++ 代码,它说没有内存泄漏。这是为什么呢?
char *p = new char[256];
delete p;
据我所知new[]
应该与delete[]
匹配。
【问题讨论】:
未定义的行为是未定义的,包括它可以工作。 难道 valgrind 不应该足够聪明地指出这一点吗?这是我可以为内存泄漏编写的最简单的测试,我希望至少有一个警告...... @DanLincan:没有内存泄漏。类似 lint 的工具可能会捡起它。 @DanLincan:你可能需要一个静态分析器来解决这个问题,它通常需要比大多数编译器都费心去追踪它的元数据。 valgrind memcheck 确实注意到了这一点,但没有将其归类为泄漏。 【参考方案1】:尽管正如@KillianDS 所说,这是未定义的行为,但差异可能与delete
和delete[]
都释放底层内存的事实有关。 delete[]
的要点是,在释放内存之前调用数组中每个对象的析构函数。由于char
是POD 并且没有析构函数,因此在这种情况下两者之间没有任何实际区别。
但是,您绝对不应该依赖它。
【讨论】:
使用非平凡析构函数delete
会崩溃,因为它在数组的开头存储元素的计数,因此指针不等于底层 free
期望的值,并且free
将引发致命断言。不同的编译器和标准 C++ 库的做法不同,但 AFAIR gcc 会这样做。
@JanHudec:“将”夸大其词了。编译器和生成的代码可以为所欲为。包括看起来在工作。
@cHao:没有夸大其词(对于 gcc),因为我查看了它生成的内容。如果您不相信我,请参阅here。 (请注意,他们只有在增加 ABI 版本时才能更改此设置,而他们已经有一段时间没有这样做了。)
@JanHudec:他们可以随时更改它,并且仍然具有符合要求的实现;他们承诺会或不会做的事情完全是另一回事,与 C++ 无关(绝对没有任何承诺)。
@cHao:是的,他们可以改变它。但是当他们改变它时,他们必须改变 ABI 版本,否则他们会破坏很多东西。【参考方案2】:
delete
和 delete[]
仅当 p
指向基本数据类型(例如 char 或 int)时才相等。
如果p
指向一个对象数组,结果会有所不同。试试下面的代码:
class T
public:
T() cout << "constructor" << endl;
~T() cout << "destructor" << endl;
;
int main()
const int NUM = 3;
T* p1 = new T[NUM];
cout << p1 << endl;
// delete[] p1;
delete p1;
T* p2 = new T[NUM];
cout << p2 << endl;
delete[] p2;
通过使用delete[]
,将调用数组中 T 的所有析构函数。通过使用delete
,只会调用p[0]
的析构函数。
【讨论】:
否则它可能会崩溃并烧毁您的 PC,毕竟这是未定义的行为。根据实现new[]
可以使用分配缓冲区的前 8 个字节来存储元素的数量,然后返回缓冲区内的偏移量;在这种情况下,delete
会将偏移地址传递给free
,这可能导致free
找不到相应的缓冲区...
@MatthieuM.: IIRC 在使用 gcc 编译时 会 崩溃(并牺牲你的 PC),因为元素的数量写在数组的开头,因此与底层 malloc()
返回的内容相比,指针发生了移动。它在使用 MSVC++ 编译时不会崩溃,这显然是从通用块元数据中得出的计数。不同之处在于微软有一个用于 C 和 C++ 的库,因此 C++ 依赖于 C 的实现细节,而 GNU stdlibc++ 尊重分层并且仅依赖于 C 库的公共接口。【参考方案3】:
当我尝试这个时,valgrind 报告:
==22707== Mismatched free() / delete / delete []
==22707== at 0x4C2B59C: operator delete(void*) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==22707== by 0x40066D: main (in /home/andrew/***/memtest)
==22707== Address 0x5a1a040 is 0 bytes inside a block of size 256 alloc'd
==22707== at 0x4C2C037: operator new[](unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==22707== by 0x40065D: main (in /home/andrew/***/memtest)
这并不是真正的内存泄漏,但 valgrind 确实注意到了这个问题。
【讨论】:
【参考方案4】:因为它是未定义的行为。在您的情况下,delete
可能会在您的编译器中完成 delete []
的工作,但它可能无法在另一台机器上工作。
【讨论】:
【参考方案5】:这是undefined behavior,所以我们无法推断它的行为。如果我们查看 C++ 标准部分草案3.7.4.2
释放函数,第 3 段说(强调我的):
[...] 否则,如果提供给标准库中 operator delete(void*) 的值不是先前调用任一 operator new(std: :size_t) 或标准库中的 operator new(std::size_t, conststd::nothrow_t&) ,如果提供给标准库中的 operator delete[] (void*) 的值不是,则行为未定义先前调用 operator new[] (std::size_t) 或 operator new[] (std::size_t, const std::nothrow_t&) 返回的值之一 标准库。
实际细节将是implementation-defined behavior,可能会有很大差异。
【讨论】:
【参考方案6】:delete
和delete []
的区别在于编译器添加了代码来调用析构函数来删除对象。说这样的话:
class A
int a;
public:
...
~A() cout<<"D'tor";
;
a=new A[23];
delete [] a;
这个delete [] a;
被转换成类似的东西,
for (int i=0; i<23; i++)
a[i].A::~A();
std::delete a;
因此,对于您的情况,由于它是内置数据类型,因此无需调用析构函数。所以,它变成了,
std::delete a;
哪个实习生调用free()
来释放内存。这就是你没有得到任何泄漏的原因。由于在 g++ 中使用 free()
完全释放了分配的内存。
但最佳做法是,如果您使用new []
,则必须使用delete []
版本。
【讨论】:
以上是关于为啥这段代码不会导致内存泄漏? [复制]的主要内容,如果未能解决你的问题,请参考以下文章