这个结构如何具有sizeof == 0?


80

有一则旧帖子要求一种sizeof可以返回的结构0。来自高声誉用户的一些高分答案说,按标准,类型或变量的大小不能为0。我同意100%。

但是,有一个新的答案介绍了此解决方案:

struct ZeroMemory {
    int *a[0];
};

我本来打算对它进行投票和评论,但是在这里花费的时间使我什至可以检查我100%确信的事情。所以......我既惊喜gccclang表现出同样的结果:sizeof(ZeroMemory) == 0。更重要的是,变量的sizeof是0

ZeroMemory z{};
static_assert(sizeof(z) == 0); // Awkward...

哇...?

Godbolt链接

这怎么可能?


37
零大小数组不是标准的C ++。但是扩展。
Jarod42 '17

@ Jarod42现在很明显。
bolov

1
而且它不能在MSVC下编译
UnholySheep '17

7
现在将它们放在数组中并对它们进行指针算术运算。
CodesInChaos

嗯...如果不为0,多少字节应该不占用?
Gerhardh

Answers:


52

在将C标准化之前,只要代码从未尝试从另一个指针中减去一个指向零尺寸类型的指针,许多编译器就可以轻松处理零尺寸类型。这种类型是有用的,并且与禁止它们相比,对它们的支持更容易,更便宜。但是,其他编译器决定禁止这种类型,并且某些静态断言代码可能依赖于这样的事实,即如果代码尝试创建零大小的数组,它们将发出尖叫声。该标准的作者面临一个选择:

  1. 允许编译器以静默方式接受零尺寸的数组声明,即使在此类声明的目的是触发诊断并中止编译的情况下,并要求所有编译器(尽管不一定以静默方式)接受此类声明以生成零尺寸的对象。

  2. 允许编译器静默接受零大小的数组声明,即使在此类声明的目的是触发诊断并中止编译的情况下,也允许遇到此类声明的编译器中止编译或在闲暇时继续执行。

  3. 如果代码声明了一个零大小的数组,则要求实现发出诊断信息,但随后允许实现在闲暇时中止编译或继续进行编译(使用它们认为合适的任何语义)。

该标准的作者选择了#3。因此,即使标准禁止使用此类构造,在标准禁止它们之前,零扩展数组声明仍被标准“扩展”视为。

C ++标准允许存在空对象,但是为了允许将空对象的地址用作标记,它要求它们的最小大小为1。对于没有成员的对象,其大小为。因此0将违反标准。但是,如果对象包含零大小的成员,则C ++标准对包含如何声明该对象的程序必须触发诊断的事实不加任何要求。由于大多数使用此类声明的代码都期望生成的对象的大小为零,因此,对于接收此类代码的编译器,最有用的行为就是以这种方式对待它们。


“在C标准化之前”,您是说C99存在之前?
星际之门

1
@Stargateur:我的意思是从发明C到编写C89标准之间大约有15年的时间。C99添加了有限形式的零尺寸对象,以避免围绕其禁止零尺寸数组而工作的一些麻烦,但是,如果C89没有在其中烦人地禁止零尺寸数组,则不需要这样的麻烦。第一名。
超级猫

“这些类型有用”它们如何有用?
user109923 '19

1
@ user109923:例如:struct polygon { int count; POINT sides[0];}; struct { struct polygon poly; POINT pts[3]; } myTriangle = { {3}, {{1,1},{2,2},{2,1} };。一个人需要格外小心,以确保对齐不会引起问题,但是一个人可以将具有不同大小的静态持续时间的对象互换处理。
超级猫

42

正如Jarod42所指出的,零尺寸数组不是标准的C ++,而是GCC和Clang扩展。

添加-pedantic会产生以下警告:

5 : <source>:5:12: warning: zero size arrays are an extension [-Wzero-length-array]
    int *a[0];
           ^

我总是忘记了std=c++XX(而不是std=gnu++XX)并不能禁用所有扩展名。

这仍然不能解释sizeof行为。但是至少我们知道这不是标准的...


1
无论哪种方式,这都是令人惊讶的,因为即使是一个空结构也应该产生非零的sizeof值...
WF

2
有趣的是,如果您创建两个自动实例,它们将sizeof(int*)分开存储(我假设):coliru.stacked-crooked.com/a/e9a3038bf587b65c
eerorika

9
这只能回答零大小数组是非标准数组,但不能解释您提出的问题。

1
根据标准,该行为不必具有任何意义-只需对编译器实现者有意义。编译器已经允许以标准禁止的方式定义类型。因此,标准中关于sizeof该类型结果给出的要求也不适用。因此,行为是由编译器开发人员决定的。
彼得

1
这个想法是ZeroMemory* z = (ZeroMemory*)malloc(sizeof(ZeroMemory) + 2 * sizeof(int*)); z.a[1] = new int{1}; /* or whatever, just use indices into a to access dynamic memory after the previous members (in this case: none) */当然的,这不是标准兼容的!(例如,这可以用于通过转换为结构轻松解析动态长度网络消息)。
hoffmale

19

在C ++中,零大小的数组是非法的。

ISO / IEC 14882:2003 8.3.4 / 1:

[..]如果存在常量表达式(5.19),则它应为整数常量表达式,并且其值应大于零。常量表达式指定数组的边界(元素的数量)。如果常量表达式的值为N,则该数组的N元素编号0N-1,并且标识符的类型D为“ T的派生声明类型列表数组N”。[..]

g ++要求该-pedantic标志在大小为零的数组上给出警告。


5

零长度数组是GCC和Clang的扩展。应用于sizeof零长度数组的结果为零

C ++类(空)不能具有size 0,但是请注意,该类ZeroMemory不是空的。它具有一个具有大小的命名成员,0应用sizeof将返回零。


5
所以...一个空类不能有大小0,但是一个非空类可以有大小0...这没有多大意义。但是我想这是非标准扩展所带来的那种冲突情况。
bolov

@bolov; 按照空类的标准,c ++类(空)的大小不能0。C ++标准表示,因为没有其他方法可以生成大小结构0。在这种特殊情况下,结构体self的成员自身的大小为0,这使结构体0的大小成为可能。我知道其令人困惑,但我尽力解释。

3
@bodov:但这不是C ++类,而是G ++类,因此C ++规则不必对其应用。特别要注意的是,您不能拥有这种类型的数组,也不能对其指针进行数学运算,因此通常的理由也不能认为大小不能为零。
Ben Voigt
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.