C ++ new int [0]-会分配内存吗?


240

一个简单的测试应用程序:

cout << new int[0] << endl;

输出:

0x876c0b8

因此,看起来好像可行。标准对此有何说法?“分配”空的内存块总是合法的吗?


4
+1非常有趣的问题-尽管我不确定实际代码中有多重要。
Zifre

37
@Zifre:我是出于好奇,但是在现实世界中可能很重要,例如,当以某种方式计算分配的内存块的大小,并且计算结果可能为零时,则无需直接添加异常不分配零大小的块。因为应该分配它们并删除它们而不会出现错误(如果未取消引用零大小的块)。因此,通常,这可以更广泛地抽象出什么是存储块。

2
@ emg-2:在您的示例情况下,实际上没有关系,因为delete []在NULL指针上是完全合法的:-)。
埃文·特兰

2
它只是切线相关的-所以我在这里评论-但是C ++在许多方面确保了不同的对象具有唯一的地址...即使它们不需要显式地需要存储。一个相关的实验将是检查一个空结构的大小。或该结构的数组。
德鲁·多曼

2
详细说明Shmoopty的评论:特别是在使用模板编程时(例如,策略类模板,如std :: allocator),在C ++中通常使用零尺寸的对象。通用代码可能需要动态分配此类对象,并使用指向它们的指针来比较对象身份。这就是为什么运算符new()返回零大小请求的唯一指针的原因。尽管重要性/通用性可能较低,但相同的推理适用于数组分配和运算符new []()。
Trevor Robinson 2010年

Answers:


233

从5.3.4 / 7起

当直接新声明器中表达式的值为零时,将调用分配函数以分配不包含任何元素的数组。

从3.7.3.1/2起

取消解引用作为零大小请求返回的指针的效果是不确定的。

即使[by new]请求的空间大小为零,请求也可能失败。

这意味着您可以执行此操作,但是您不能合法地(在所有平台上以明确定义的方式)取消对所获得的内存的引用-您只能将其传递给array delete-并且应该删除它。

这是3.7.3.1.2后面的句子的一个有趣的脚注(即,不是标准的规范性部分,但出于说明目的而包括在内)

[32。目的是通过调用malloc()或calloc()使操作符new()可以实现,因此规则基本相同。C ++与C的不同之处在于C ++要求零请求以返回非null指针。


如果不删除,则会发生内存泄漏。是预期的吗?至少我没想到。
EralpB 2014年

12
@EralpB:相反,即使您的请求为零,此分配也会在堆上发生,其中一个请求意味着几个簿记操作,例如在分配器给定的区域之前和之后分配和初始化堆保护,插入到空闲列表或其他复杂的恐怖结构。释放它意味着进行向后簿记。
v.oddou 2014年

3
@EralpB是的,我想每当您不平衡A new[]delete[]-大小时,您都可以期待内存泄漏。特别是,在调用时,new[i]您需要的内存比要分配的内存要多一些,以存储数组的大小(以后delete[]在分配时使用)
pqnet

24

是的,这样分配零大小的数组是合法的。但是您还必须删除它。


1
您对此有引文吗?众所周知,int ar[0];为什么新可以吗?
Motti

4
有趣的是,C ++在禁止零尺寸对象方面没有那么强大。想想空基类优化:在这里,空基类子对象的大小也可能为零。相比之下,C标准付出了巨大的努力以确保永远不会创建零大小的对象:例如,在定义malloc(0)时,它的作用是使用一些非零参数运行malloc。并在结构{...; T n []; }; 当没有为数组(FAM)分配空间时,它表示其行为就像“ n”具有一个元素。(在两种情况下,以任何方式使用对象都是UB,例如xn [0])
Johannes Schaub-litb

1
我认为sizeof (type)预计永远不会返回零。参见例如:stackoverflow.com/questions/2632021/can-sizeof-return-0-zero
pqnet

甚至所谓的“空基类优化”也只是相关的,因为它坚持要求所有对象(与对象中的所有字节相对)必须具有唯一的地址。如果需要真正关心具有唯一地址的对象的代码以确保它们的大小为非零,则可以简化C ++。
超级猫

15

标准对此有何说法?“分配”空的内存块总是合法的吗?

每个对象都有一个唯一的标识,即一个唯一的地址,这意味着一个非零的长度(如果要求零字节,则实际的内存量将静默增加)。

如果您分配了多个这些对象,那么您会发现它们具有不同的地址。


“每个对象都有唯一的标识,即唯一的地址,这意味着长度不为零” –是,但错误。对象有地址,但是指向对象的指针可以指向随机存储器。“如果您要求零字节,则实际的内存量将以静默方式增加”-不确定。operator []还可以将数组的大小存储在某个位置(请参见isocpp.org/wiki/faq/freestore-mgmt#num-elems-in-new-array)。因此,如果实现通过数据分配字节数,则可以只分配字节数和0字节数据,返回1-past-last指针。
骏马

分配内存的长度为非零,而不是可用内存的长度为非零。我的观点是,指向两个不同对象的两个指针不应指向同一地址。
ChrisW

1
标准(open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf)确实确实说过(3.7.4.1/2),不同的调用operator new[]应返回不同的指针。顺便说一句,new expression还有一些附加规则(5.3.4)。我找不到任何线索来说明new实际上分配0大小实际上是必需的。抱歉,我不满意,因为我发现您的答案不是回答问题,而是提供一些有争议的陈述。
骏马

@使用内存空间可以隐式计算存储块长度的内存。例如,对于零大小的数组,new[]实现有可能返回没有映射动态内存的范围内的地址,因此不真正使用任何内存(使用地址空间时)
pqnet

@pqnet:高质量的实现应该允许无限数量的新/删除周期,不是吗?在具有64位指针的平台上,可以合理地说,执行while(!exitRequested) { char *p = new char[0]; delete [] p; }循环而不循环指针的计算机在可能耗尽地址空间之前会陷入尘土,但是在具有32位指针的平台上,它将是远没有合理的假设。
超级猫

14

是的,使用分配0大小块完全合法new。您根本无法做任何有用的事情,因为没有有效的数据可供您访问。int[0] = 5;是非法的。

但是,我认为,标准允许之类的东西malloc(0)来回报NULL

您仍然仍然需要delete []从分配中返回的任何指针。


5
关于malloc,您是对的-它是实现定义的。这通常被视为功能不当。

我想一个有趣的问题是:如果给定大小为0,则新的nothrow版本是否可以返回NULL?
Evan Teran

1
至少通过标准,new和malloc的语义没有以任何方式链接。

并不是说他们是...只是专门询问了new的nohrow版本。
埃文·特兰

@Evan-new的nothrow版本仅在请求失败时返回null-不仅在请求大小为0时@Neil-没有规范地链接-而是通过意图链接(即,new运算符可以用malloc来实现,而不能以其他方式实现方式)-请参阅我在答案中包含的脚注
Faisal Vali,2009年

1

奇怪的是,即使请求了零字节,C ++也要求new运算符返回合法指针。(要求这种听起来很奇怪的行为可以简化语言中其他地方的内容。)

我发现有效的C ++第三版在“项目51:编写new和delete时遵循约定”中这样说。


0

我向您保证,新的int [0]自测试以来会花费您额外的空间。

例如,

int **arr = new int*[1000000000];

明显小于

int **arr = new int*[1000000000];
for(int i =0; i < 1000000000; i++) {
    arr[i]=new int[0];
}

第二个代码段的内存使用量减去第一个代码段的内存使用量就是用于大量新int [0]的内存。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.