好吧,我想我们都同意以下代码所发生的事情是未定义的,具体取决于传递的内容,
void deleteForMe(int* pointer) { delete[] pointer; }
指针可以是各种不同的东西,因此delete[]
对它执行无条件是未定义的.但是,让我们假设我们确实传递了一个数组指针,
int main() { int* arr = new int[5]; deleteForMe(arr); return 0; }
我的问题是,在这种情况下,指针是一个数组,谁知道这个?我的意思是,从语言/编译器的角度来看,它不知道arr
数组指针是否指向单个int的指针.哎呀,它甚至不知道是否arr
是动态创建的.但是,如果我做以下事情,
int main() { int* num = new int(1); deleteForMe(num); return 0; }
操作系统足够聪明,只能删除一个int,而不是通过删除超出该点的其余内存来进行某种类型的"杀戮狂欢"(与strlen
非\0
终结字符串形成对比- 它将一直持续到它点击0).
那么他们的工作是记住这些东西吗?操作系统是否在后台保留某种类型的记录?(我的意思是,我意识到我开始这篇文章时说过发生的事情是未定义的,但事实是,'杀戮狂欢'的情况不会发生,所以因此在实际世界中有人记得.)
到目前为止给出的答案似乎没有解决的一个问题是:如果运行时库(不是OS,真的)可以跟踪数组中的事物数量,那么为什么我们需要delete[]
语法呢?为什么不能使用单个delete
表单来处理所有删除?
对此的回答可以追溯到C++作为C兼容语言的根源(它不再是真正的努力.)Stroustrup的理念是程序员不应该为他们没有使用的任何功能付费.如果他们不使用数组,那么他们不应该为每个分配的内存块承担对象数组的成本.
也就是说,如果您的代码只是这样做
Foo* foo = new Foo;
那么分配的内存空间foo
不应包括支持数组所需的任何额外开销Foo
.
由于只设置了数组分配来携带额外的数组大小信息,因此您需要告诉运行时库在删除对象时查找该信息.这就是我们需要使用的原因
delete[] bar;
而不仅仅是
delete bar;
如果bar是指向数组的指针.
对于我们大多数人(包括我自己)来说,关于几个额外字节的内存的烦恼现在看起来很古怪.但是仍然存在一些情况,即保存几个字节(从可能是非常多的内存块)可能很重要.
编译器不知道它是一个数组,它信任程序员.删除指向一个单一的int
与delete []
将导致不确定的行为.你的第二个main()
例子是不安全的,即使它没有立即崩溃.
编译器必须跟踪需要以某种方式删除的对象数.它可以通过过度分配来存储数组大小来实现.有关更多详细信息,请参阅C++ Super FAQ.
是的,操作系统会在"背景"中保留一些内容.例如,如果你跑
int* num = new int[5];
操作系统可以分配4个额外字节,在分配的内存的前4个字节中存储分配的大小并返回偏移指针(即,它分配内存空间1000到1024但指针返回指向1004,位置1000- 1003存储分配的大小).然后,当调用delete时,它可以在指针传递给它之前查看4个字节以查找分配的大小.
我确信还有其他方法可以跟踪分配的大小,但这是一种选择.
这与此问题非常相似,它有许多您正在寻找的细节.
但足以说明,追踪任何此类操作系统并不是操作系统的工作.它实际上是运行时库或底层内存管理器,它们将跟踪数组的大小.这通常通过预先分配额外内存并将阵列的大小存储在该位置(大多数使用头节点)来完成.
通过执行以下代码,可以在某些实现中查看
int* pArray = new int[5]; int size = *(pArray-1);
delete
或者delete[]
可能都释放分配的内存(内存指向),但最大的区别是delete
数组上不会调用数组中每个元素的析构函数.
无论如何,混合new/new[]
,delete/delete[]
可能是UB.
它不知道它是一个数组,这就是为什么你必须提供delete[]
而不是常规的旧delete
.
我有一个类似的问题.在C中,使用malloc()(或其他类似函数)分配内存,并使用free()将其删除.只有一个malloc(),它只分配一定数量的字节.只有一个free(),它只是将一个指针作为它的参数.
那么为什么在C中你可以将指针移交给free,但是在C++中你必须告诉它它是一个数组还是一个变量?
我已经知道,答案与类析构函数有关.
如果你分配一个MyClass类的实例...
classes = new MyClass[3];
并使用delete删除它,您可能只获得调用的MyClass的第一个实例的析构函数.如果使用delete [],则可以确保将为数组中的所有实例调用析构函数.
这是重要的区别.如果你只是使用标准类型(例如int),你将不会真正看到这个问题.另外,你应该记住在new []和delete []上使用delete的行为是未定义的 - 它可能在每个编译器/系统上的工作方式不同.