关于删除,delete,delete[],operator delete()等

4

可能存在重复问题:
delete[]如何“知道”操作数数组的大小?
(POD)释放内存:delete[]和delete相同吗?

据我所知,以下内容

class A {};
A* a = new A;
//
delete A;

首先调用operator new()(全局的或由A提供的专门的)来分配正确数量的内存,然后调用A的构造函数。当调用delete时,首先调用A的析构函数,然后调用operator delete()释放“正确数量的内存”。
根据TC++PL所述,“正确数量的内存”是这样确定的:
要释放由new分配的空间,delete和delete[]必须能够确定分配的对象的大小。这意味着使用标准实现的new分配的对象将占用略多于静态对象的空间。通常,一个字用于保存对象的大小。
这很有道理。但是这个字存储在哪里以便delete访问?就在新指针指向的地址之前吗?这样delete可以通过访问a-sizeof<void*>来获取要删除的大小吗?
你能澄清一下吗?
我认为这个答案可能有助于我理解delete []如何工作。我了解new []的工作原理,并且delete []将首先调用“数组中所有对象”的析构函数,然后释放所有这些内存...
但是delete []如何知道数组的大小?
谢谢您的有益答案!

我认为这取决于具体的实现,但更有见识的人肯定会更具体。 - ereOn
1个回答

3
这完全取决于实现方式。大多数运行时确实会在返回的内存地址之前存储内存大小((BYTE *)p-sizeof(size_t)),但还有其他选择。在我自己的内存管理器中(是的,我写这种东西),我有一个更复杂的数据结构(使用指向链表、校验和等的指针),放在返回的内存之前。实际上,存储这些信息的位置由内存管理器决定。
除了已分配内存的大小,new[] 还会存储实例数量,以便知道要调用多少个析构函数。这通常不属于内存管理器的范畴,而是由 C++ 运行时 / 编译器本身处理。但同样,存储实例数量的位置也取决于编译器,尽管在实践中,我希望这些信息会存储在返回的内存之前(并且在内存管理器存储的任何数据之后)。 编辑: 下面的小实用程序显示了在分配内存之前的内存布局:
#include <iostream>

typedef unsigned char Byte;

class X
   {
   public:
      X() : m_value(1) {}
      ~X() {m_value = 0;}
   private:
      int m_value;
   };

void print(Byte *p,int offset)
{
printf ("Value at %d: 0x%x (%d)\n", offset, p[offset], p[offset]);
}

void main()
{
X *x = new X[10];

std::cout << "Address of x: " << x << std::endl;
std::cout << "sizeof(X)   : " << sizeof(X) << std::endl;

Byte *p = (Byte *)x;
print(p,-1);
print(p,-2);
print(p,-3);
print(p,-4);
print(p,-5);
print(p,-6);
print(p,-7);
print(p,-8);
print(p,-9);
print(p,-10);

X *y = new X;
std::cout << "Address of y: " << y << std::endl;

p = (Byte *)y;
print(p,-1);
print(p,-2);
print(p,-3);
print(p,-4);
print(p,-5);
print(p,-6);
print(p,-7);
print(p,-8);
print(p,-9);
print(p,-10);
}

在Visual Studio 2005中运行此代码将得到以下输出:
Address of x: 00481DE4
sizeof(X)   : 4
Value at -1: 0x0 (0)
Value at -2: 0x0 (0)
Value at -3: 0x0 (0)
Value at -4: 0xa (10)
Value at -5: 0xc (12)
Value at -6: 0x0 (0)
Value at -7: 0x2f (47)
Value at -8: 0x8 (8)
Value at -9: 0x2f (47)
Value at -10: 0x98 (152)
Address of y: 00481E70
Value at -1: 0xc (12)
Value at -2: 0x0 (0)
Value at -3: 0x2f (47)
Value at -4: 0x8 (8)
Value at -5: 0x2a (42)
Value at -6: 0x98 (152)
Value at -7: 0xf8 (248)
Value at -8: 0xb0 (176)
Value at -9: 0x0 (0)
Value at -10: 0x48 (72)

您可以清楚地看到,在第一种情况(通过new[]创建的数组)中,使用了4个字节来表示元素的数量(0、0、0、10,这些数字组合在一起形成了值10)。

在第二种情况下,这些字节被省略了,我们看到的模式与第一种情况相同(12、0、47、8)。我不确定Visual C++确切地存储了分配的字节数量,但它证明了元素数量确实是在返回指针之前存储的(在Visual Studio 2005中)。


那么你的意思是对于 new OBJECT[SIZE],我们通常可以在内存中获得类似于 " SIZEOF(OBJECT) SIZE OBJECT" 的东西? - Cedric H.
1
实际上,SIZEOF(OBJECT)是指对象所占用的字节数,NOFELEMENTS是用于delete[]操作符中知道需要调用多少个析构函数。而SIZEOF(OBJECT)被内存管理器实现用来知道需要释放多少字节。 - Patrick
谢谢。但奇怪的是,在那里(http://www.parashift.com/c++-faq-lite/compiler-dependencies.html#faq-38.7),他们没有提到“SIZEOF(OBJECT)”:s - Cedric H.
1
更正之前的评论:应该是NOFELEMENTS而不是NOFBYTESALLOCATED。如果你分配了一个数组,那么NOFBYTESALLOCATED就是SIZEOF(OBJECT)*NOFELEMENTS。他们在FaqLite中没有提到sizeof(object)的原因是sizeof(object)或者分配的字节数与需要调用多少个析构函数无关(尽管你可以从nofbytes和sizeof(object)推导出元素的数量)。分配的字节数只与管理内存的模块有关,而不是C++运行时需要知道要调用多少个析构函数。 - Patrick

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接