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

Posted

技术标签:

【中文标题】C++ new int[0]——它会分配内存吗?【英文标题】:C++ new int[0] -- will it allocate memory? 【发布时间】:2009-07-06 13:42:29 【问题描述】:

一个简单的测试应用:

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

输出:

0x876c0b8

所以看起来它有效。标准对此有何评论? “分配”空的内存块总是合法的吗?

【问题讨论】:

+1 非常有趣的问题 - 虽然我不确定它在实际代码中的重要性。 @Zifre:我问的是好奇心,但这在现实世界中可能很重要,例如当以某种方式计算分配的内存块的大小时,计算结果可能为零,则不需要直接添加不分配零大小块的异常。因为它们应该被分配和删除而没有错误(如果只有零大小的块不被取消引用)。所以通常这会更广泛地抽象出内存块是什么。 @emg-2:在您的示例情况下,这实际上并不重要,因为 delete[] 在 NULL 指针上是完全合法的 :-)。 它只是切线相关的——所以我在这里评论——但 C++ 在许多方面确保了不同的对象具有唯一的地址......即使它们没有明确需要存储。一个相关的实验是检查空结构的大小。或该结构的数组。 详细说明 Shmoopty 的评论:尤其是在使用模板(例如 std::allocator 等策略类模板)编程时,C++ 中的对象大小为零是很常见的。通用代码可能需要动态分配此类对象并使用指向它们的指针来比较对象身份。这就是为什么 operator new() 为零大小的请求返回唯一指针的原因。虽然可以说不太重要/不常见,但同样的推理也适用于数组分配和运算符 new[]()。 【参考方案1】:

从 5.3.4/7 开始

当 direct-new-declarator 中表达式的值为零时,调用分配函数来分配一个没有元素的数组。

从 3.7.3.1/2 开始

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

还有

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

这意味着你可以这样做,但你不能合法地(以在所有平台上明确定义的方式)取消引用你获得的内存 - 你只能将它传递给数组删除 - 你应该删除它。

这是一个有趣的脚注(即不是标准的规范部分,但出于说明目的而包含在内)附在 3.7.3.1/2 的句子

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

【讨论】:

如果我不删除,就会出现内存泄漏。是预期的吗?至少我没想到。 @EralpB:相反,即使您的请求为零,此分配也会发生在堆上,其中一个请求意味着多个簿记操作,例如在给定区域之前和之后分配和初始化堆守卫通过分配器,插入到空闲列表或其他复杂的可怕结构中。释放它意味着向后记账。 @EralpB 是的,我猜你每次不平衡new[]delete[] 时都会发生内存泄漏——无论大小。特别是,当您调用new[i] 时,您需要比您尝试分配的内存更多的内存来存储数组的大小(稍后在解除分配时由delete[] 使用) 另外,由于递增指针以指向内存块中最后一个元素之后的一个元素是合法的,因此递增指向这个零大小内存块的指针应该是合法的。【参考方案2】:

是的,像这样分配一个大小为零的数组是合法的。但你也必须删除它。

【讨论】:

您对此有引用吗?我们都知道int ar[0];是非法的,为什么new可以? 有趣的是,C++ 在禁止零大小对象方面并不那么强大。考虑空基类优化:同样,空基类子对象的大小可能为零。相比之下,C 标准努力确保永远不会创建零大小的对象:例如,在定义 malloc(0) 时,它表示效果是使用一些非零参数运行 malloc。并在 struct ...; T n[]; ;当没有为数组(FAM)分配空间时,它表示它的行为就像“n”有一个元素一样。 (在这两种情况下,以任何方式使用对象都是UB,比如x.n[0]) 我认为sizeof (type) 预计永远不会返回零。例如:***.com/questions/2632021/can-sizeof-return-0-zero 即使是所谓的“空基类优化”也只是因为坚持所有对象(而不是对象中的所有字节)必须具有唯一的地址。如果需要真正关心具有唯一地址的对象的代码来确保它们具有非零大小,则 C++ 可以变得更简单。【参考方案3】:

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

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

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

【讨论】:

“每个对象都有一个唯一的身份,即一个唯一的地址,这意味着一个非零长度”——正确,但错误。对象有地址,但指向对象的指针可以指向随机内存。 “如果您要求零字节,实际内存量将默默增加” - 不确定。 operator [] 还会在某处存储数组的大小(请参阅isocpp.org/wiki/faq/freestore-mgmt#num-elems-in-new-array)。因此,如果实现为数据分配字节数,它可以只为字节数分配数据,为数据分配 0 个字节,返回 1-past-last 指针。 分配内存的非零长度,而不是可用内存的非零长度。我的观点是指向两个不同对象的两个指针不应该指向同一个地址。 标准 (open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf) 确实说 (3.7.4.1/2) 对 operator new[] 的不同调用应该返回不同的指针。顺便说一句,new expression 有一些附加规则 (5.3.4)。我找不到任何线索表明大小为 0 的 new 实际上是 required 来分配任何东西的。抱歉,我投了反对票,因为我发现您的回答没有回答问题,而是提供了一些有争议的陈述。 @Steed 存储块长度的内存可以使用地址空间隐式计算。例如,对于零大小的数组,new[] 实现可能会返回没有动态内存映射的范围内的地址,因此实际上不使用任何内存(在使用地址空间时) @pqnet:一个高质量的实现应该允许无限数量的新/删除周期,不是吗?在具有 64 位指针的平台上,可以合理地说,在没有回收指针的情况下执行 while(!exitRequested) char *p = new char[0]; delete [] p; 循环的计算机会在地址空间耗尽之前崩溃成灰尘,但在具有 32 位指针的平台上,将是一个不太合理的假设。【参考方案4】:

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

但是,我相信标准允许像 malloc(0) 这样的东西返回 NULL

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

【讨论】:

关于 malloc,你是对的 - 它是实现定义的。这通常被视为错误特征。 我想一个有趣的问题是:如果给定大小为 0,new 的 nothrow 版本能否返回 NULL? new 和 malloc 的语义没有任何联系,至少按照标准。 不是说他们是...是专门询问新的 nothrow 版本。 @Evan - new 的 nothrow 版本仅在请求失败时返回 null - 不仅仅是请求大小为 0 @Neil - 不是标准链接 - 而是按意图链接(即可以实现 operator new就 malloc 而言,但不是相反)-请参阅我的答案中包含的脚注【参考方案5】:

奇怪的是,C++ 要求 operator new 返回一个合法的指针 即使请求零字节。 (需要这个听起来很奇怪的 行为简化了语言中其他地方的事情。)

我发现Effective C++ 第三版在“第51条:写new和delete时要遵守约定”这样说。

【讨论】:

【参考方案6】:

我向您保证,新的 int[0] 会花费您额外的空间,因为我已经对其进行了测试。

例如,

的内存使用情况
int **arr = new int*[1000000000];

明显小于

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

第二个代码sn-p的内存使用量减去第一个代码sn-p的内存使用量就是无数个new int[0]使用的内存。

【讨论】:

以上是关于C++ new int[0]——它会分配内存吗?的主要内容,如果未能解决你的问题,请参考以下文章

C++ new和delete运算符简介

C++/C 语言独立内存分配

如何在 C++ 中使用 new 运算符初始化内存?

如何在 C++ 中使用 new 运算符初始化内存?

c++中new和delete的用法

C++二维数组内存分配