在 C++ 中,数组分配的 new 和 new[] 有什么区别
In C++, what is the difference between new and new[] for array allocations
我知道 C++ 中 free 和 delete 的区别。但是我一直不明白的一件事是为什么在 C malloc/free 中可以分配取消分配单个 'objects' 和数组但是在 C++ 中我们需要使用正确的 new/delete 与 new[]/delete[ ]对.
在 Whosebug 上搜索,似乎在 C++ 中,new[] 分配额外的内存来保存分配的数组的大小,而 new 只将内存分配给对象本身。因此,您应该注意这种额外的开销。
如果前一段确实如此,那么malloc/free如何处理这个开销?或者他们只是接受这个开销?如果在 C 中可以容忍,为什么在 C++ 中不能?
另一方面,万一不是因为内存开销,而是因为调用构造函数和析构函数,编译器就不能足够聪明地在底层生成适当的代码,让程序员只写 new/delete 单个对象和对象数组?
我正在为语义类似于 C++ 的玩具语言编写编译器,似乎可以让编译器决定如何仅使用 new/delete 分配和取消分配,但作为 C++使用 new/delete 和 new[]/delete[],也许有一个我现在没有看到的问题。也许与多态性和虚拟表有关?
如果你很好奇,我天真的想法是简单地分配一个整数和 object/array ,其中这个整数是数组的大小或者简单的 1 作为一个对象。然后,当调用 delete 时,它检查整数的值,如果它是 1,它调用析构函数。如果它大于 1,则它迭代调用数组中每个对象的析构函数的数组。正如我所说,它似乎可以工作并且让程序员只写 new/delete 而不是 new/delete 与 new[]/delete。但话又说回来,也许有一个我没有看到的问题。
编辑部分:
经过一些回答,我决定尝试提供一些伪代码和更好的背景。
在 C 语言中,内存分配通常使用 malloc() 进行,释放使用 free()。无论是分配单个 POD、单个结构还是数组,malloc() 都适合所有这些情况。如果您分配的是单个结构,则不需要不同版本的 malloc() ;如果您分配的是数组,则不需要 malloc_array() 版本。至少在 public API 级别。换句话说,无论是分配几个字节还是多个字节似乎都没有关系,不会有用于簿记分配大小信息的开销。
你们很多人都知道,包括我自己,new 和 delete 做的不仅仅是分配和取消分配内存。 New 分配内存并调用构造函数,delete 调用析构函数,然后取消分配内存。但是在 C++ 中,您需要注意是只分配单个对象还是对象数组。如果您正在分配数组,则需要使用 new[]/delete[] 对。
在 C 中,如果您实现二叉树,节点将使用 malloc 分配并使用 free 取消分配,而在 C++ 中使用 new 和 delete。但是如果你在 C++ 中实现向量 class 之类的东西,在 C 中你仍然会使用 malloc/free,但现在在 C++ 中你需要使用 new[]/delete[](考虑一个合理的实现没有太多的黑魔法)。
考虑以下由编译器执行的伪代码。在这个伪代码中,删除函数以某种方式访问了 malloc 内部并知道有多少字节,这反过来又可以很容易地用来计算有多少对象。由于此删除实现使用 malloc 内部机制来了解分配了多少内存,理论上应该没有簿记开销。
// ClassType is a meta type only know by the compiler
// it stores a class info such as name, size, constructors and so on
void *new(ClassType c) {
// allocates memory with malloc. Malloc() do the storage bookkeeping
// note that somehow malloc is allocating just a single object
c *ptr = malloc(sizeof(c));
// now, call the constructor of the requested class
c.constructor(ptr);
// return the new object
return ptr;
}
void *new(ClassType c, size_t n) {
c *ptr = malloc(sizeof(c) * n);
// iterate over the array and construct each object
for (i = 0; i < n; ++i) {
c.constructor(ptr[i]);
}
return ptr;
}
// this delete version seems to be able to de-allocate both single
// objects and arrays of objects with no overhead of bookkeeping because
// the bookkeeping is made by malloc/free. So I would need
// just a new/delete pair instead of new/delete vs new[]/delete[]
// Why C++ doesn't use something like my proposed implementation?
// What low-level details prohibits this implementation from working?
void delete(ClassType c, void *ptr) {
// get raw information of how many bytes are used by ptr;
size_t n = malloc_internals_get_size(ptr);
// convert the number of bytes to number of objects in the array
n = c.bytesToClassSize(n);
c* castedPointer = (c*) ptr;
// calls the destructor
for (i = 0; i < n; ++i) {
c.destructor(castedPointer[i]);
}
// free memory chunk
free(ptr);
}
why in C malloc/free can allocate de-allocate both single 'objects'
Malloc 不创建任何对象。它分配不包含任何对象的“原始内存”。相应地,free
不会破坏任何对象。 new
表达式创建对象,delete
销毁对象,而 delete[]
销毁对象数组。
为了让语言实现知道有多少对象需要被 delete[]
销毁,这个数字必须存储在某个地方。为了让语言实现知道有多少对象需要被 delete
销毁,这个数字 而不是 需要存储在任何地方,因为它总是一个。
存储号码不是免费的,存储未使用的号码是不必要的开销。存在不同形式的删除,以便语言实现可以销毁正确数量的对象,而不必存储非数组创建的对象数量 new
.
then how malloc/free handles this overhead?
malloc/free 没有这个开销,因为它不创建或销毁对象。因此,没有什么需要处理的。
存储 malloc 确实需要处理的已分配字节数存在类似的问题。没有用于分配或释放单个字节的类似单独函数。这可能是因为这样的用例可能很少见。 Malloc 有更聪明的方法来处理这个存储,因为分配比需要更多的内存是不可观察的,而这种技巧对于对象的数量是不可能的,因为对象的创建和销毁是可观察的(至少在非平凡类型的情况下) .
new
通常通过内部使用 malloc 来处理存储分配字节数的问题。
couldn't the compiler be smart enough to generate the appropriate code under the hood
并非没有某种开销,不。有开销是的,它可以。
But then again, maybe there's a catch that I am not seeing.
我不确定这是不是你没见过的问题,但你的想法的问题是即使分配了单个对象也会分配整数的开销。
并非所有实现在 new/delete 与 new[]/delete[] 之间都有区别,但它们存在是因为 new 表达式在数组的情况下表现不同。问题是 new 表达式不仅仅是调用 new 运算符,底层运算符可能是相同的:
#include <iostream>
// class-specific allocation functions
struct X {
static void* operator new(std::size_t sz)
{
std::cout << "custom new for size " << sz << '\n';
return ::operator new(sz);
}
static void* operator new[](std::size_t sz)
{
std::cout << "custom new[] for size " << sz << '\n';
return ::operator new(sz);
}
};
int main() {
X* p1 = new X;
delete p1;
X* p2 = new X[10];
delete[] p2;
}
但它们也可能超载到不同的。对于本机编译器来说,这可能是重要的区别,如果您正在编写解释器,或者如果您不希望用户修改 new\delete,您可以采用不同的方式来执行此操作。在 C++ new 表达式中,在使用提供的函数分配内存后调用一个构造函数,new[] 表达式将调用多个并存储计数。当然,如果编译器有面向对象的内存模型,就像 Java 那样,那么数组将是一个大小为 属性 的对象。单个对象只是一个实例的数组。正如您所说,我们不需要单独的删除表达式。
一些巧妙的 malloc 实现实际上并不跟踪每次分配的大小(通过巧妙地使用四舍五入),因此它具有极低的 space 开销。他们会分配一大块内存,只要开发人员分配 <64 字节,它就会只使用这个块的下一个 64 字节,并在别处标记一个位来跟踪这个 64 字节的块现在正在使用中.即使用户只想分配 1 个字节,它也会分配一整块。这意味着每个分配只有一个 bit 开销,因此每 8 个分配都有一个共享的 byte 开销。几乎没有。 (还有比这更聪明的策略,这只是一个简化的例子)
new
和 delete
可以共享这个超低开销的实现,因为 delete
知道总是销毁一个对象,而不管 space 它的数量其实有。这又是超快的,并且 space 开销很低。
delete[]
不能那样做,因为它必须确切 知道要调用多少析构函数。因此它必须跟踪数组中有多少项,最多 std::size_t
,这意味着必须向每个 new[]
添加 ~4 个字节。如果数据需要对齐 >4,则每个分配还必须在计数和数组中的第一项之间浪费填充字节。 delete[]
因此必须知道如何查看填充,找到计数,因此它确切地知道要销毁多少对象。每次分配都需要时间和更多 space。
C++ 使您可以在“始终有效,但更慢且更大”和“仅适用于一项,但更快且更小”之间进行选择,因此程序可以 运行 尽可能快。
我知道 C++ 中 free 和 delete 的区别。但是我一直不明白的一件事是为什么在 C malloc/free 中可以分配取消分配单个 'objects' 和数组但是在 C++ 中我们需要使用正确的 new/delete 与 new[]/delete[ ]对.
在 Whosebug 上搜索,似乎在 C++ 中,new[] 分配额外的内存来保存分配的数组的大小,而 new 只将内存分配给对象本身。因此,您应该注意这种额外的开销。
如果前一段确实如此,那么malloc/free如何处理这个开销?或者他们只是接受这个开销?如果在 C 中可以容忍,为什么在 C++ 中不能?
另一方面,万一不是因为内存开销,而是因为调用构造函数和析构函数,编译器就不能足够聪明地在底层生成适当的代码,让程序员只写 new/delete 单个对象和对象数组?
我正在为语义类似于 C++ 的玩具语言编写编译器,似乎可以让编译器决定如何仅使用 new/delete 分配和取消分配,但作为 C++使用 new/delete 和 new[]/delete[],也许有一个我现在没有看到的问题。也许与多态性和虚拟表有关?
如果你很好奇,我天真的想法是简单地分配一个整数和 object/array ,其中这个整数是数组的大小或者简单的 1 作为一个对象。然后,当调用 delete 时,它检查整数的值,如果它是 1,它调用析构函数。如果它大于 1,则它迭代调用数组中每个对象的析构函数的数组。正如我所说,它似乎可以工作并且让程序员只写 new/delete 而不是 new/delete 与 new[]/delete。但话又说回来,也许有一个我没有看到的问题。
编辑部分:
经过一些回答,我决定尝试提供一些伪代码和更好的背景。
在 C 语言中,内存分配通常使用 malloc() 进行,释放使用 free()。无论是分配单个 POD、单个结构还是数组,malloc() 都适合所有这些情况。如果您分配的是单个结构,则不需要不同版本的 malloc() ;如果您分配的是数组,则不需要 malloc_array() 版本。至少在 public API 级别。换句话说,无论是分配几个字节还是多个字节似乎都没有关系,不会有用于簿记分配大小信息的开销。
你们很多人都知道,包括我自己,new 和 delete 做的不仅仅是分配和取消分配内存。 New 分配内存并调用构造函数,delete 调用析构函数,然后取消分配内存。但是在 C++ 中,您需要注意是只分配单个对象还是对象数组。如果您正在分配数组,则需要使用 new[]/delete[] 对。
在 C 中,如果您实现二叉树,节点将使用 malloc 分配并使用 free 取消分配,而在 C++ 中使用 new 和 delete。但是如果你在 C++ 中实现向量 class 之类的东西,在 C 中你仍然会使用 malloc/free,但现在在 C++ 中你需要使用 new[]/delete[](考虑一个合理的实现没有太多的黑魔法)。
考虑以下由编译器执行的伪代码。在这个伪代码中,删除函数以某种方式访问了 malloc 内部并知道有多少字节,这反过来又可以很容易地用来计算有多少对象。由于此删除实现使用 malloc 内部机制来了解分配了多少内存,理论上应该没有簿记开销。
// ClassType is a meta type only know by the compiler
// it stores a class info such as name, size, constructors and so on
void *new(ClassType c) {
// allocates memory with malloc. Malloc() do the storage bookkeeping
// note that somehow malloc is allocating just a single object
c *ptr = malloc(sizeof(c));
// now, call the constructor of the requested class
c.constructor(ptr);
// return the new object
return ptr;
}
void *new(ClassType c, size_t n) {
c *ptr = malloc(sizeof(c) * n);
// iterate over the array and construct each object
for (i = 0; i < n; ++i) {
c.constructor(ptr[i]);
}
return ptr;
}
// this delete version seems to be able to de-allocate both single
// objects and arrays of objects with no overhead of bookkeeping because
// the bookkeeping is made by malloc/free. So I would need
// just a new/delete pair instead of new/delete vs new[]/delete[]
// Why C++ doesn't use something like my proposed implementation?
// What low-level details prohibits this implementation from working?
void delete(ClassType c, void *ptr) {
// get raw information of how many bytes are used by ptr;
size_t n = malloc_internals_get_size(ptr);
// convert the number of bytes to number of objects in the array
n = c.bytesToClassSize(n);
c* castedPointer = (c*) ptr;
// calls the destructor
for (i = 0; i < n; ++i) {
c.destructor(castedPointer[i]);
}
// free memory chunk
free(ptr);
}
why in C malloc/free can allocate de-allocate both single 'objects'
Malloc 不创建任何对象。它分配不包含任何对象的“原始内存”。相应地,free
不会破坏任何对象。 new
表达式创建对象,delete
销毁对象,而 delete[]
销毁对象数组。
为了让语言实现知道有多少对象需要被 delete[]
销毁,这个数字必须存储在某个地方。为了让语言实现知道有多少对象需要被 delete
销毁,这个数字 而不是 需要存储在任何地方,因为它总是一个。
存储号码不是免费的,存储未使用的号码是不必要的开销。存在不同形式的删除,以便语言实现可以销毁正确数量的对象,而不必存储非数组创建的对象数量 new
.
then how malloc/free handles this overhead?
malloc/free 没有这个开销,因为它不创建或销毁对象。因此,没有什么需要处理的。
存储 malloc 确实需要处理的已分配字节数存在类似的问题。没有用于分配或释放单个字节的类似单独函数。这可能是因为这样的用例可能很少见。 Malloc 有更聪明的方法来处理这个存储,因为分配比需要更多的内存是不可观察的,而这种技巧对于对象的数量是不可能的,因为对象的创建和销毁是可观察的(至少在非平凡类型的情况下) .
new
通常通过内部使用 malloc 来处理存储分配字节数的问题。
couldn't the compiler be smart enough to generate the appropriate code under the hood
并非没有某种开销,不。有开销是的,它可以。
But then again, maybe there's a catch that I am not seeing.
我不确定这是不是你没见过的问题,但你的想法的问题是即使分配了单个对象也会分配整数的开销。
并非所有实现在 new/delete 与 new[]/delete[] 之间都有区别,但它们存在是因为 new 表达式在数组的情况下表现不同。问题是 new 表达式不仅仅是调用 new 运算符,底层运算符可能是相同的:
#include <iostream>
// class-specific allocation functions
struct X {
static void* operator new(std::size_t sz)
{
std::cout << "custom new for size " << sz << '\n';
return ::operator new(sz);
}
static void* operator new[](std::size_t sz)
{
std::cout << "custom new[] for size " << sz << '\n';
return ::operator new(sz);
}
};
int main() {
X* p1 = new X;
delete p1;
X* p2 = new X[10];
delete[] p2;
}
但它们也可能超载到不同的。对于本机编译器来说,这可能是重要的区别,如果您正在编写解释器,或者如果您不希望用户修改 new\delete,您可以采用不同的方式来执行此操作。在 C++ new 表达式中,在使用提供的函数分配内存后调用一个构造函数,new[] 表达式将调用多个并存储计数。当然,如果编译器有面向对象的内存模型,就像 Java 那样,那么数组将是一个大小为 属性 的对象。单个对象只是一个实例的数组。正如您所说,我们不需要单独的删除表达式。
一些巧妙的 malloc 实现实际上并不跟踪每次分配的大小(通过巧妙地使用四舍五入),因此它具有极低的 space 开销。他们会分配一大块内存,只要开发人员分配 <64 字节,它就会只使用这个块的下一个 64 字节,并在别处标记一个位来跟踪这个 64 字节的块现在正在使用中.即使用户只想分配 1 个字节,它也会分配一整块。这意味着每个分配只有一个 bit 开销,因此每 8 个分配都有一个共享的 byte 开销。几乎没有。 (还有比这更聪明的策略,这只是一个简化的例子)
new
和 delete
可以共享这个超低开销的实现,因为 delete
知道总是销毁一个对象,而不管 space 它的数量其实有。这又是超快的,并且 space 开销很低。
delete[]
不能那样做,因为它必须确切 知道要调用多少析构函数。因此它必须跟踪数组中有多少项,最多 std::size_t
,这意味着必须向每个 new[]
添加 ~4 个字节。如果数据需要对齐 >4,则每个分配还必须在计数和数组中的第一项之间浪费填充字节。 delete[]
因此必须知道如何查看填充,找到计数,因此它确切地知道要销毁多少对象。每次分配都需要时间和更多 space。
C++ 使您可以在“始终有效,但更慢且更大”和“仅适用于一项,但更快且更小”之间进行选择,因此程序可以 运行 尽可能快。