在C ++中为零大小的分配返回唯一地址背后的理由是什么?

时间:2018-05-30 17:17:55

标签: c++ c language-lawyer standards new-operator

在C ++中为零大小的分配返回唯一地址的理由是什么?

背景:C11标准讲述了malloc(7.20.3内存管理功能):

  

如果请求的空间大小为零,则行为是实现定义的:返回空指针,或者行为就像大小是非零值一样,但返回的指针不应用于访问一个对象。

也就是说,正如我所看到的那样,malloc总是成功进行零大小的分配,因为只能使用零大小分配的指针调用一些其他内存分配函数,如{{1}用它:

  • 如果free返回mallocNULL就可以了,所以这可以被认为是成功的,
  • 如果它返回一些其他值,那也是成功的(因为它不是free(NULL)),唯一的条件是该值的NULL也应该有效。

另外,C11(也是7.20.3)没有指定malloc返回的地址必须是唯一的,只是它们必须指向不相交的内存区域:

  

如果分配成功,则返回指针,以便将其分配给指向任何类型对象的指针,然后用于在分配的空间中访问此类对象或此类对象的数组(直到空格为明确解除分配)。分配对象的生命周期延长   从分配到解除分配。每个这样的分配都应该产生一个指向与任何其他对象不相交的对象的指针。

所有零大小的对象都是不相交的AFAICT,这意味着free可以为多个零大小的分配返回相同的指针(例如malloc就可以了),或者每次都有不同的指针,或某些指针的相同指针等。

然后C ++ 98带来了两个原始内存分配函数:

NULL

请注意,这些函数仅返回原始内存:它们不会创建或初始化任何类型的任何AFAICT对象。

你这样称呼它们:

void* operator new(std::size_t size);
void* operator new(std::size_t size, std::align_val_t alignment);

C ++ 17标准的#include <iostream> #include <new> int main() { void* ptr = operator new(std::size_t{0}); std::cout << ptr << std::endl; operator delete(ptr, std::size_t{0}); return 0; } 部分解释了它们,但我看到的关键保证在[new.delete.single]中给出:

  

即使请求的空间大小为零,请求也可能失败。如果请求成功,则返回的值应为非空指针值(7.11)p0,与先前返回的值p1不同,除非该值p1随后传递给运算符delete。此外,对于21.6.2.1和21.6.2.2中的库分配函数,p0应表示与调用者可访问的任何其他对象的存储不相交的存储块的地址。通过作为零大小请求返回的指针的间接效果是未定义的.38

也就是说,他们必须总是在成功时返回不同的指针。这与[basic.stc.dynamic.allocation]相比有点变化。

我的问题是:此更改背后的理由是什么?(即在C ++中为零大小的分配返回唯一地址)

理想情况下,答案只是与论文(或其他来源)的链接,该论文探讨了替代方案并激发了他们的语义。通常我会针对这些C ++ 98问题参加C ++的设计和演变,但是第10节(内存管理)没有提到它的任何内容。否则,某种权威的参考会很好。

免责声明:我asked it on reddit但我没有得到足够好的答案,所以我没有得到任何有用的答案。我想请你,如果你只有一个假设,请随意发表它作为答案,但请注意这只是一个假设。

另外,在reddit上,人们继续讨论零大小的类型,我是否有改变标准的提议等等。这个问题是关于传递大小等于零的原始内存分配函数的语义。如果零大小类型的主题与您的答案相关,请加入它们!但是尽量不要因为切线问题而脱轨。

此外,在reddit上,人们也提出了“为了优化目的”这样的论点而没有真正提及任何更具体的内容。我希望在答案中比“因为优化”更具体。例如,一个redditor提到了别名优化,但我想知道哪种别名优化适用于无法解除引用的指针,并且无法让任何人对此发表评论。因此,如果您要提及优化,可能会出现一个小例子来展示它的讨论。

2 个答案:

答案 0 :(得分:8)

问题是C ++中的对象(无论大小)必须具有唯一标识。因此,不同的共存对象(无论其大小)必须具有不同的地址,因为两个比较相等的指针被假定为指向同一个对象

如果您承认零大小的对象可以具有相同的地址,则无法再区分两个地址是否是同一个对象。

关于“新的不返回对象”问题的许多评论。

请在此背景下忘记OOP术语:

C ++规范对“对象”一词的含义有一个精确的定义。

CPP Reference:Object

特别是:

  

C ++程序创建,销毁,引用,访问和操作对象。   C ++中的对象是具有

的存储区域      
      
  • 尺寸(可以用sizeof确定);
  •   
  • 对齐要求(可以使用alignof确定);
  •   
  • 存储持续时间(自动,静态,动态,线程本地);
  •   
  • 一生(以储存期限或临时为限);
  •   
  • 型;
  •   
  • value(可能是不确定的,例如对于默认初始化的非类类型);
  •   
  • 可选地,名称。
  •   
     

以下实体不是对象:值,引用,函数,   枚举器,类型,非静态类成员,位域,模板,类或   函数模板特化,命名空间,参数包以及它。

     

变量是不是非静态数据成员的对象或引用,   这是由声明引入的。

     

对象由definitions,new-expressions,throw-expression,when创建   更改联合的活动成员以及临时对象的位置   需要。

答案 1 :(得分:1)

原因很简单,代码不需要特殊处理边界条件。我想说的很多,算法必须处理零大小的对象作为边界条件。不太常见的是将指针与对象进行比较以查看它们是否是同一个对象的算法,但即使对于零大小的对象,这仍然有效。

但是,您的问题假设这是一个变化。除了1980年代后期的短暂中断之外,我所知道的所有C和C ++实现总是表现得像这样。

dmr的原始C编译器表现得像这样但是在1987年左右,草案C标准指定零大小对象的malloc返回NULL。这真的很奇怪,甚至最终的C-89标准也使它实现了定义,但我从未遇到过这样一个可怕的事情。

我在my blog的“&#34; Malloc Madness&#34;”中详细讨论了这个问题。