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

278

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

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

输出:

0x876c0b8

看起来它工作了。标准对此有何规定?"分配"空的内存块始终合法吗?


4
非常有趣的问题,尽管我不确定它在实际代码中有多大意义。 - Zifre
44
@Zifre: 我是出于好奇在问,但在现实世界中这可能很重要,例如当计算分配的内存块的大小时,计算结果可能为零,那么就没有直接需要添加异常以防止分配大小为零的块。因为它们应该在不出错的情况下被分配和删除(只要不引用大小为零的块)。所以,总的来说,这扩展了内存块的概念。 - anon
2
在你的例子中,实际上并不重要,因为delete[]在空指针上是完全合法的 :-). - Evan Teran
2
这只是有些相关,所以我在这里发表评论 - 但是在许多方面,C++ 确保不同的对象具有唯一的地址...即使它们不需要显式存储。一个相关的实验是检查空结构体的大小。或者该结构体的数组。 - Drew Dormann
2
为了阐述Shmoopty的评论:特别是在使用模板编程时(例如std::allocator这样的策略类模板),在C++中拥有零大小的对象是很常见的。通用代码可能需要动态分配这些对象并使用指针来比较对象标识。这就是为什么operator new()为零大小的请求返回唯一指针。虽然可能不太重要/常见,但同样的推理也适用于数组分配和operator new。 - Trevor Robinson
显示剩余3条评论
6个回答

260

来自5.3.4/7

当直接的new声明符(expression)的值为零时,调用分配函数以分配一个没有元素的数组。

来自3.7.3.1/2

对返回大小为零请求的指针进行解引用的效果是未定义的。

此外

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

这意味着您可以这样做,但是您不能在所有平台上以合法的方式解引用获得的内存-您只能将其传递给数组delete-并且您应该删除它。

这是一个有趣的脚注(即标准的非规范性部分,但包含说明性内容)附加到来自3.7.3.1/2的句子:

[32. 意图是通过调用malloc()或calloc()来实现operator new(),因此规则基本相同。 C ++不同于C,需要零请求返回非空指针。]


2
如果我不删除,就会出现内存泄漏。这是否是预期的?至少我没有预料到。 - EralpB
12
相反,即使您的请求为零,此分配也会发生在堆上,其中一个请求意味着进行多个簿记操作,例如在分配器给定的区域之前和之后分配和初始化堆保护区域,在空闲列表中插入或其他复杂可怕的结构中。释放它意味着进行反向簿记。 - v.oddou
3
@EralpB 是的,我想你应该期望每次不平衡使用 new[]delete[] 时会出现内存泄漏 - 无论大小如何。特别是,当您调用 new[i] 时,需要比您尝试分配的内存稍微多一点,以存储数组的大小(在释放时由 delete[] 使用)。 - pqnet
另外,由于将指针递增以指向内存块中最后一个元素的下一个元素是合法的,因此将指向此零大小内存块的指针递增应该也是合法的。 - ghd
3
在零大小的情况下,开始指针就是超出结束指针一位,因此您无法对其进行递增。 - David Stone
@DavidStone: 感谢您纠正我。的确,我认为除了与自身比较之外,使用指向零大小内存位置的指针无法执行任何操作。 - ghd

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

1
你有这个引用吗?我们都知道 int ar[0]; 是非法的,为什么 new 是可以的呢? - Motti
4
有趣的是,C++对禁止大小为零的对象并不是那么强制。考虑到空基类优化:在这种情况下,一个空的基类子对象可能具有大小为零的尺寸。相比之下,C标准非常努力地确保永远不会创建大小为零的对象:在定义malloc(0)时,它表示其效果是使用某些非零参数运行malloc,例如。在结构体{...; T n [];}中,当未为数组分配空间(即FAM)时,它表示其行为好像“n”具有一个元素。(在这两种情况下,以任何方式使用对象都是未定义行为,例如x.n[0])。 - Johannes Schaub - litb
1
我认为 sizeof(type) 应该永远不会返回零。例如请参考:https://dev59.com/inE85IYBdhLWcg3wvGOm - pqnet
即使所谓的“空基类优化”也只是因为坚持认为所有对象(而不是对象内的所有字节)必须具有唯一地址才相关。如果需要确保它们具有非零大小的代码实际上关心对象具有唯一地址,那么C++本可以更简单。 - supercat

15

标准上对此有什么规定吗?“分配”空块内存是否总是合法的?

每个对象都有唯一的身份(即唯一的地址),这意味着它们具有非零长度(如果您请求零字节,则实际内存量将被静默增加)。

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


每个对象都有一个唯一的标识,即一个唯一的地址,这意味着它具有非零长度 - 这是正确的,但不完全准确。对象具有地址,但指向对象的指针可能会指向随机内存。 如果您请求零字节,则实际内存量将被静默增加 - 不确定。operator [] 也在某个地方存储数组的大小(请参见https://isocpp.org/wiki/faq/freestore-mgmt#num-elems-in-new-array)。因此,如果实现使用数据分配字节数,则可以仅为字节数和0字节的数据分配,返回1-past-last指针。 - Steed
分配的内存长度不为零,可用内存长度也不为零。我的观点是,两个指向不同对象的指针不应该指向相同的地址。 - ChrisW
1
标准文档(http://www.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时实际上是“必须”分配任何内容的。抱歉,我投了反对票,因为我发现你的回答没有回答问题,而是提供了一些有争议的声明。 - Steed
@Steed,用于存储块长度的内存空间可以通过地址空间隐式计算。例如,对于大小为零的数组,“new[]”实现可能会返回一个地址范围,在该范围内不存在映射的动态内存,因此不会真正使用任何内存(但使用了地址空间)。 - pqnet
@Steed 请查看此页面:https://www.cplusplus.com/reference/new/operator%20new[]/ “如果该参数为零,该函数仍将在成功时返回一个明显的非空指针(尽管解引用此指针会导致未定义行为)。” - guan boshen
显示剩余5条评论

14

是的,使用new分配一个大小为0的块是完全合法的。但你无法对此做出任何有用的操作,因为没有有效的数据可供访问。尝试int[0] = 5;就是不合法的。

然而,我相信标准允许像malloc(0)这样的操作返回NULL

无论如何,你仍需要delete []从分配中获得的指针。


5
关于malloc,你是正确的——它是实现定义的。这经常被视为一个缺陷。 - anon
我认为一个有趣的问题是:如果给定大小为0,nothrow版本的new能否返回NULL? - Evan Teran
1
new和malloc的语义没有任何关联,至少按照标准是这样。 - anon
并不是说它们...我特别在问nothrow版本的new。 - Evan Teran
@Evan - new 的 nothrow 版本仅在请求失败时返回 null,而不仅仅是请求大小为 0 时返回。@Neil - 没有规范链接,但意图上有链接(即 operator new 可以基于 malloc 实现,但反过来不行)- 请参见我回答中包含的脚注。 - Faisal Vali

3
我保证你,新建一个 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] 的内存使用量。

2
引人注意的是,即使请求0字节,C++也要求operator new返回一个合法指针。(这种听起来奇怪的行为在语言的其他地方简化了事情。)《Effective C++第三版》在“条款51:编写new和delete时遵守约定”中提到了这一点。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接