我读到过delete[]
运算符是必需的,因为运行时环境不能保留分配块是否为需要调用析构函数的对象的信息,但它确实可以保留分配块存储在内存中的位置和块的大小。
只需要一个额外的元数据位来记住是否需要在delete时调用析构函数,那么为什么不这样做呢?
我很确定有一个很好的解释,我不怀疑它,我只想知道它。
我读到过delete[]
运算符是必需的,因为运行时环境不能保留分配块是否为需要调用析构函数的对象的信息,但它确实可以保留分配块存储在内存中的位置和块的大小。
只需要一个额外的元数据位来记住是否需要在delete时调用析构函数,那么为什么不这样做呢?
我很确定有一个很好的解释,我不怀疑它,我只想知道它。
我认为原因在于C++不会强制你做任何你不想做的事情。如果加入额外的元数据,但有人没有使用它,这将强制施加额外的开销,与C ++语言设计目标相矛盾。
当你需要描述的功能时,C++确实提供了一种方法。它被称为std::vector
,你应该几乎总是优先选择它、另一种类型的容器或智能指针,而不是裸指针的new
和delete
。
std::vector
……并不完全是因为它可以动态增长。一个更简单的容器也可以工作。尽管如此,已经足够接近了。 - Thomas Edingmalloc()
作为::operator new()
,并且使用free()
作为::operator delete()
(或多或少)。标准的malloc
/free
不提供记录元素数量所需的簿记,并且无法在free
时间确定malloc
的大小。在每个单独的对象之间添加另一层内存操作,从C/C++的角度来看,这是一个相当大的复杂性/抽象度的跃升。此外,将这种开销添加到每个对象中会破坏一些内存管理方法,因为它们是根据对象的大小进行设计的。malloc
保留了你要求的精确大小是错误的。实际上,malloc
只关心提供足够大的内存块。尽管出于效率原因它可能不会过度分配,但仍然可能给您一个“标准”大小的内存块,例如一个2^n
字节的块。因此,实际分配的对象数量(也就是实际大小)实际上是未知的。// A plain array of doubles:
+-------+-------+-------
| 0 | 1 | 2
+-------+-------+-------
// A tentative to stash our extra bit
+-------++-------++-------++
| 0 || 1 || 2 ||
+-------++-------++-------++
// A correction since we introduced alignment issues
// Note: double's aligment is most probably its own size
+-------+-------+-------+-------+-------+-------
| 0 | bit | 1 | bit | 2 | bit
+-------+-------+-------+-------+-------+-------
嗯!
编辑
因此,在大多数平台上(地址很重要的地方),您需要“扩展”每个指针,并实际上将它们的大小加倍(对齐问题)。
所有指针都增加一倍大小以便您可以存储额外的位,这样做是否可行?对于大多数人来说,我想是可以的。但是,C++不是为大多数人设计的,它主要是为那些关心性能的人设计的,无论是速度还是内存,因此这是不可接受的。
编辑结束
那么正确的答案是什么?正确的答案是恢复类型系统丢失的信息是昂贵的。不幸的是。
int * p = new int;
定义删除此内容的唯一方法是调用int *
上的 delete。因此,从您知道它是int
的事实中隐含地知道了大小。(请注意,在void *
上调用 delete 是 UB:https://dev59.com/questions/gHNA5IYBdhLWcg3wfN6O#941959)。 - Thomas Edingdelete
和delete[]
两个运算符都接受void*
类型的参数,它们并不知道具体的类型。 - Gene Bushuyevvoid *
是UB),或者是通过多态对象的vtable在析构函数中传递。 - Simon Richtervoid*
参数无关,标准解释了如果删除的指针不是由适当的new
或new[]
创建的有效对象,则编译器和运行时不需要验证或执行任何特殊操作。 - Gene Bushuyev