How does delete[] know it's an array?
好吧,我想我们都同意以下代码所发生的事情是未定义的,这取决于传递的内容,
1 2 3 4 | void deleteForMe(int* pointer) { delete[] pointer; } |
指针可以是各种不同的东西,因此对它执行无条件的
1 2 3 4 5 6 | int main() { int* arr = new int[5]; deleteForMe(arr); return 0; } |
我的问题是,在这种情况下,指针是一个数组,谁知道呢?我的意思是,从语言/编译器的角度来看,它不知道
1 2 3 4 5 6 | int main() { int* num = new int(1); deleteForMe(num); return 0; } |
操作系统足够智能,只删除一个int,而不会通过删除超过该点的其余内存来进行某种类型的"杀戮狂潮"(与
那么,记住这些东西是谁的工作呢?操作系统在后台保存某种类型的记录吗?(我的意思是,我意识到我开始写这篇文章的时候说发生的事情是不确定的,但事实是,"杀戮狂潮"的场景不会发生,因此在现实世界中,有人会记住。)
到目前为止给出的答案似乎没有解决的一个问题是:如果运行库(实际上不是操作系统)能够跟踪数组中的事物数量,那么为什么我们需要
这个问题的答案可以追溯到C++作为C兼容语言的根源(它不再是真正的努力)。StruouUp的哲学是程序员不必为他们不使用的任何特性付费。如果他们不使用数组,那么就不必为每个分配的内存块承担对象数组的成本。
也就是说,如果您的代码
1 | Foo* foo = new Foo; |
那么,为
由于只设置了数组分配来携带额外的数组大小信息,因此在删除对象时,需要告诉运行库查找这些信息。这就是为什么我们需要使用
1 | delete[] bar; |
而不仅仅是
1 | delete bar; |
如果bar是指向数组的指针。
对于我们大多数人(包括我自己在内),对额外几个字节的记忆的挑剔最近似乎很奇怪。但是在某些情况下,保存几个字节(从可能非常多的内存块中)仍然很重要。
编译器不知道它是一个数组,它信任程序员。使用
编译器必须跟踪有多少对象需要以某种方式删除。它可以通过过度分配足够的空间来存储数组大小来实现这一点。有关详细信息,请参阅C++超级常见问题解答。
是的,操作系统将一些东西保存在"后台"。例如,如果运行
1 | int* num = new int[5]; |
操作系统可以分配4个额外的字节,将分配的大小存储在分配的内存的前4个字节中,并返回一个偏移指针(即,它分配内存空间1000到1024,但指针返回到1004,位置1000-1003存储分配的大小)。然后,当调用delete时,它可以在指针传递给它之前查看4个字节,以找到分配的大小。
我确信还有其他方法可以跟踪分配的大小,但这是一种选择。
这与这个问题非常相似,并且有许多您正在寻找的细节。
但可以这么说,跟踪这些信息并不是操作系统的工作。实际上,运行库或底层内存管理器将跟踪数组的大小。这通常是通过在前面分配额外的内存并将数组的大小存储在该位置(大多数情况下使用头节点)来完成的。
通过执行以下代码可以在某些实现中查看
1 2 | int* pArray = new int[5]; int size = *(pArray-1); |
总之,混合
它不知道它是一个数组,这就是为什么你必须提供
我有一个类似的问题。在C中,使用malloc()(或其他类似的函数)分配内存,并使用free()删除它。只有一个malloc(),它只分配一定数量的字节。只有一个free(),它只接受一个指针作为参数。
那么为什么在C中你可以把指针交给空闲的,但是在C++中你必须告诉它它是数组还是单个变量?
我知道答案与类析构函数有关。
如果您分配一个类MyClass的实例…
1 | classes = new MyClass[3]; |
并且用delete删除它,您只能为调用的MyClass的第一个实例获取析构函数。如果使用delete[],可以确保将为数组中的所有实例调用析构函数。
这是重要的区别。如果您只是使用标准类型(例如int),那么您不会真正看到这个问题。另外,您应该记住,在new[]上使用delete和在new上使用delete[]的行为是未定义的——它在每个编译器/系统上的工作方式可能不同。
编译器的一种方法是分配更多的内存,并将元素计数存储在head元素中。
示例如何完成:在这里
1 | int* i = new int[4]; |
编译器将分配sizeof(int)*5字节。
1 | int *temp = malloc(sizeof(int)*5) |
将在第一个
1 | *temp = 4; |
设置
1 | i = temp + 1; |
因此,
和
1 | delete[] i; |
将按以下方式处理
1 2 3 4 5 | int *temp = i - 1; int numbers_of_element = *temp; // = 4 ... call destructor for numbers_of_element elements if needed ... that are stored in temp + 1, temp + 2, ... temp + 4 free (temp) |
这取决于负责内存分配的运行时,就像您可以用free删除标准C中用malloc创建的数组一样。我认为每个编译器实现它是不同的。一种常见的方法是为数组大小分配一个额外的单元。
但是,运行时不够智能,无法检测它是否是数组或指针,您必须通知它,如果您弄错了,您要么没有正确删除(例如,ptr而不是array),要么您最终对大小取了一个不相关的值,并造成重大损坏。
从语义上讲,C++中的两个版本的删除运算符都可以"吃"任何指针;但是,如果指向一个对象的指针被赋给EDCOX1(0),那么UB就会出现,这意味着任何可能发生的事情,包括系统崩溃或根本没有发生任何事情。
C++要求程序员根据分配的主题选择删除操作符的正确版本:数组或单个对象。
如果编译器可以自动判断传递给删除操作符的指针是否是指针数组,那么C++中只有一个删除运算符,这对两种情况都足够了。
同意编译器不知道它是否是数组。这取决于程序员。
编译器有时会通过过度分配存储数组大小来跟踪需要删除多少对象,但并非总是必要的。
对于一个完整的规范,当分配额外的存储时,请参阅C++ABI(编译器是如何实现的):ItAuthC+ABI:数组操作符新Cookie
"未定义的行为"仅仅意味着语言规范没有让高卢人知道将会发生什么。这并不意味着会发生坏事。
So whose job is it to remember these things? Does the OS keep some type of record in the background? (I mean, I realise that I started this post by saying that what happens is undefined, but the fact is, the 'killing spree' scenario doesn't happen, so therefore in the practical world someone is remembering.)
这里通常有两层。底层内存管理器和C++实现。
一般来说,内存管理器会记住(除其他外)分配的内存块的大小。这可能比所请求的C++实现的块大。通常,内存管理器会将其元数据存储在分配的内存块之前。
C++实现通常只会记住数组的大小,如果需要这样做的话,通常是因为该类型具有非平凡析构函数。
因此对于具有普通析构函数的类型,"delete"和"delete[]"的实现通常是相同的。C++实现简单地将指针传递给底层内存管理器。类似的东西
1 | free(p) |
另一方面,对于具有非平凡析构函数"delete"和"delete[]"的类型,可能是不同的。"delete"是类似的(其中t是指针指向的类型)
1 2 | p->~T(); free(p); |
而"delete[]"则是类似的。
1 2 3 4 5 6 7 | size_t * pcount = ((size_t *)p)-1; size_t count = *count; for (size_t i=0;i<count;i++) { p[i].~T(); } char * pmemblock = ((char *)p) - max(sizeof(size_t),alignof(T)); free(pmemblock); |
不能对数组使用delete,也不能对非数组使用delete[]。
嘿,好吧,这取决于在分配内置类型或类/结构的数组时使用新的[]表达式分配的内容,并且不提供构造函数和析构函数。运算符将把它视为大小"size of(object)*numobjects",而不是对象数组,因此在这种情况下,分配的对象数将不会存储在但是,在这里,如果您分配对象数组,并且在对象中提供构造函数和析构函数而不是行为更改,则新表达式将多分配4个字节,并在前4个字节中存储对象数,以便可以调用其中每一个的析构函数,因此新的[]表达式将返回向前移动4个字节的指针,而不是在返回内存时,delete[]表达式将首先调用函数模板,遍历对象数组,并为每个对象调用析构函数。我创建了这个简单的代码开关,它重载新的[]和删除[]表达式,并提供了一个模板函数来释放内存,并在需要时为每个对象调用析构函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 | // overloaded new expression void* operator new[]( size_t size ) { // allocate 4 bytes more see comment below int* ptr = (int*)malloc( size + 4 ); // set value stored at address to 0 // and shift pointer by 4 bytes to avoid situation that // might arise where two memory blocks // are adjacent and non-zero *ptr = 0; ++ptr; return ptr; } ////////////////////////////////////////// // overloaded delete expression void static operator delete[]( void* ptr ) { // decrement value of pointer to get the //"Real Pointer Value" int* realPtr = (int*)ptr; --realPtr; free( realPtr ); } ////////////////////////////////////////// // Template used to call destructor if needed // and call appropriate delete template<class T> void Deallocate( T* ptr ) { int* instanceCount = (int*)ptr; --instanceCount; if(*instanceCount > 0) // if larger than 0 array is being deleted { // call destructor for each object for(int i = 0; i < *instanceCount; i++) { ptr[i].~T(); } // call delete passing instance count witch points // to begin of array memory ::operator delete[]( instanceCount ); } else { // single instance deleted call destructor // and delete passing ptr ptr->~T(); ::operator delete[]( ptr ); } } // Replace calls to new and delete #define MyNew ::new #define MyDelete(ptr) Deallocate(ptr) // structure with constructor/ destructor struct StructureOne { StructureOne(): someInt(0) {} ~StructureOne() { someInt = 0; } int someInt; }; ////////////////////////////// // structure without constructor/ destructor struct StructureTwo { int someInt; }; ////////////////////////////// void main(void) { const unsigned int numElements = 30; StructureOne* structOne = nullptr; StructureTwo* structTwo = nullptr; int* basicType = nullptr; size_t ArraySize = 0; /**********************************************************************/ // basic type array // place break point here and in new expression // check size and compare it with size passed // in to new expression size will be the same ArraySize = sizeof( int ) * numElements; // this will be treated as size rather than object array as there is no // constructor and destructor. value assigned to basicType pointer // will be the same as value of"++ptr" in new expression basicType = MyNew int[numElements]; // Place break point in template function to see the behavior // destructors will not be called and it will be treated as // single instance of size equal to"sizeof( int ) * numElements" MyDelete( basicType ); /**********************************************************************/ // structure without constructor and destructor array // behavior will be the same as with basic type // place break point here and in new expression // check size and compare it with size passed // in to new expression size will be the same ArraySize = sizeof( StructureTwo ) * numElements; // this will be treated as size rather than object array as there is no // constructor and destructor value assigned to structTwo pointer // will be the same as value of"++ptr" in new expression structTwo = MyNew StructureTwo[numElements]; // Place break point in template function to see the behavior // destructors will not be called and it will be treated as // single instance of size equal to"sizeof( StructureTwo ) * numElements" MyDelete( structTwo ); /**********************************************************************/ // structure with constructor and destructor array // place break point check size and compare it with size passed in // new expression size in expression will be larger by 4 bytes ArraySize = sizeof( StructureOne ) * numElements; // value assigned to"structOne pointer" will be different // of"++ptr" in new expression "shifted by another 4 bytes" structOne = MyNew StructureOne[numElements]; // Place break point in template function to see the behavior // destructors will be called for each array object MyDelete( structOne ); } /////////////////////////////////////////// |
只需在类内定义一个析构函数,并用这两种语法执行代码
1 2 3 | delete pointer delete [] pointer |
根据输出U可以找到解决方案
答案是:
int*parray=new int[5];
int大小=*(parray-1);
上面发布的内容不正确,产生的值无效。"-1"表示元素在64位Windows操作系统上,正确的缓冲区大小驻留在ptr-4字节地址中。