如果在C / C ++中定义一个0大小的数组,会发生什么情况?


127

只是好奇,如果我int array[0];在代码中定义零长度数组,实际上会发生什么?GCC一点也不抱怨。

样例程序

#include <stdio.h>

int main() {
    int arr[0];
    return 0;
}

澄清度

我实际上是想弄清楚是否以这种方式初始化了零长度数组,而不是像在Darhazer的注释中指出的那样,对可变长度进行了优化。

这是因为我必须向野外发布一些代码,所以我试图确定是否必须处理SIZE定义为的情况0,这在某些静态定义的代码中发生int array[SIZE];

实际上,我很惊讶GCC没有投诉,这引起了我的疑问。从我收到的答案中,我认为缺少警告的主要原因是支持未使用新的[]语法更新的旧代码。

因为我主要是想知道错误,所以我将隆丁的答案标记为正确的(纳瓦兹是第一个,但还不够完整),其他人则指出了它在尾部填充结构中的实际用途是“正确的”。正是我想要的。


51
@AlexanderCorwin:不幸的是,在C ++中,由于行为不确定,非标准扩展名和其他异常,尝试自己做点事情通常不是通往知识的途径。
本杰明·林德利

5
@JustinKirk我也只是通过测试并确保它起作用而陷入了困境。而且由于我在帖子中受到的批评,我了解到测试和测试它并不意味着它是合法和合法的。因此,自检有时无效。
StormByte

2
@JustinKirk,有关在何处使用它的示例,请参见Matthieu的答案。在数组大小为模板参数的模板中,它也可能派上用场。问题中的示例显然与上下文无关。
Mark Ransom'3

2
@JustinKirk:[]Python甚至""C语言的目的是什么?有时,您有一个需要数组的函数或宏,但没有任何数据可放入其中。
dan04

15
什么是“ C / C ++”?这是两种不同的语言
轻轨比赛(于2012年

Answers:


86

数组的大小不能为零。

ISO 9899:2011 6.7.6.2:

如果表达式是一个常量表达式,则其值应大于零。

上面的文本对于纯数组都是正确的(第1段)。对于VLA(可变长度数组),如果表达式的值小于或等于零(第5段),则行为不确定。这是C标准中的规范性文本。不允许编译器以不同的方式实现它。

gcc -std=c99 -pedantic 对非VLA情况发出警告。


34
“它实际上必须给出一个错误”-标准中未识别“警告”和“错误”之间的区别(仅提及“诊断”),并且是唯一必须停止编译的情况(即,实际情况之间的差异)在警告和错误之间]是在遇到#error指令时。
Random832 2012年

12
仅供参考,(C或C ++)标准仅规定了编译器必须允许的内容,而没有规定编译器必须禁止的内容。在某些情况下,他们将声明编译器应发出“诊断”信息,但具体取决于他们得到的信息。其余的留给编译器供应商。编辑:Random832也说了什么。
mcmcc 2012年

8
@Lundin“不允许编译器构建包含零长度数组的二进制文件。” 该标准绝对没有说明。它只是说,当给定的源代码包含大小为零长度常量表达式的数组时,它必须至少生成一条诊断消息。该标准唯一禁止编译器构建二进制文件的情况是,如果它遇到#error预处理程序指令。
Random832'3

5
@Lundin为所有正确的情况生成二进制文件都满足#1,对于错误的情况生成或不生成二进制文件都不会影响它。打印警告对于#3是足够的。此行为与#2不相关,因为该标准并未定义此源代码的行为。
Random832 2012年

13
@Lundin:重点是您的陈述是错误的;只要发出诊断,符合标准的编译器可以构建包含零长度数组的二进制文件。
基思·汤普森

85

按照标准,这是不允许的。

但是,C编译器目前的惯例是将这些声明视为灵活数组成员(FAM声明:

C99 6.7.2.1,§16:作为特殊情况,结构中具有多个命名成员的最后一个元素可能具有不完整的数组类型;这称为灵活数组成员。

FAM的标准语法为:

struct Array {
  size_t size;
  int content[];
};

这个想法是,然后您将其分配为:

void foo(size_t x) {
  Array* array = malloc(sizeof(size_t) + x * sizeof(int));

  array->size = x;
  for (size_t i = 0; i != x; ++i) {
    array->content[i] = 0;
  }
}

您也可以静态使用它(gcc扩展名):

Array a = { 3, { 1, 2, 3 } };

这也称为尾部填充结构(此术语早于C99标准的发布)或结构破解(感谢Joe Wreschnig指出)。

但是,这种语法直到最近才在C99中标准化(并保证了效果)。在需要恒定大小之前。

  • 1 是便携式的方式,尽管这很奇怪。
  • 0 更好地表明了意图,但就标准而言,它不是合法的,并且被某些编译器(包括gcc)作为扩展来支持。

但是,尾部填充练习依赖于以下事实:存储可用(请注意malloc),因此通常不适合堆栈使用。


@Lundin:我在这里没有看到任何VLA,所有大小在编译时都是已知的。该柔性阵列术语来自gcc.gnu.org/onlinedocs/gcc-4.1.2/gcc/Zero-Length.html和DOE资格int content[];,据我了解这里。由于我不太了解C的艺术术语……您能确认我的推理是否正确?
Matthieu M. 2012年

@MatthieuM .: C99 6.7.2.1,第16节:作为特殊情况,结构中具有多个命名成员的最后一个元素可能具有不完整的数组类型;这称为灵活数组成员。
Christoph

这个惯用语也被称为“ struct hack”,并且我遇到的人比“ tail-papped structure”更熟悉(以前从未听说过,除了可能作为填充结构的通用参考以实现未来的ABI兼容性)。 )或我在C99中首次听到的“柔性阵列成员”。

1
对struct hack使用数组大小​​为1可以避免编译器发出嘎嘎叫声,但这只是“便携式的”,因为编译器编写者非常乐于将这种用法视为事实上的标准。不是因为禁止零尺寸数组,而是因为程序员随后使用单元素数组作为拙劣的替代品,也不是因为编译器作者的历史态度是即使标准不要求它们也应满足程序员的需求,编译器作者可以很容易地和有效地优化foo[x]foo[0]每当foo是一个单元素数组。
2015年

1
@RobertSsupportsMonicaCellio:如答案中明确所示,但在末尾。我也将说明放在首位,以便一目了然。
Matthieu M.

58

在标准C和C ++,大小为零的阵列允许..

如果您使用的是GCC,请使用-pedanticoption进行编译。它会发出警告,说:

zero.c:3:6: warning: ISO C forbids zero-size array 'a' [-pedantic]

对于C ++,它会给出类似的警告。


9
在Visual C ++ 2010中:error C2466: cannot allocate an array of constant size 0
Mark Ransom

4
-Werror只是将所有警告变成错误,不能解决GCC编译器的错误行为。
伦丁2012年

C ++ Builder 2009还正确给出了一个错误:[BCC32 Error] test.c(3): E2021 Array must have at least one element
Lundin

1
取而代之的是-pedantic -Werror,您也可以这样做-pedantic-errors
Stephan Dollberg,2012年

3
零大小的数组与零大小的数组不太一样std::array。(此外:我记得但找不到CLA中考虑并明确拒绝VLA的来源。)

27

这是完全非法的,而且一直存在,但是许多编译器忽略了发出错误信号。我不确定为什么要这么做。我知道的一种用法是从布尔值触发编译时错误:

char someCondition[ condition ];

如果condition为假,则出现编译时错误。因为编译器确实允许这样做,所以我开始使用:

char someCondition[ 2 * condition - 1 ];

这给出的大小为1或-1,而我从未找到过接受-1大小的编译器。


这是一个有趣的技巧。
Alex Koay 2012年

10
我认为这是元编程中的常见技巧。如果STATIC_ASSERT使用它,我不会感到惊讶。
James Kanze 2012年

为什么不这样:#if condition \n #error whatever \n #endif
Jerfov2 '16

1
@ Jerfov2,因为在预处理时可能不知道条件,而仅在编译时
知道

9

我要补充一点,关于该参数,有一整页的gcc在线文档。

一些引号:

GNU C中允许使用零长度数组。

在ISO C90中,您必须将内容长度设置为1

3.0之前的GCC版本允许零长度数组进行静态初始化,就好像它们是灵活的数组一样。除了有用的情况外,它还允许在可能损坏以后数据的情况下进行初始化

所以你可以

int arr[0] = { 1 };

和繁荣:-)


我可以这样做int a[0],那么a[0] = 1 a[1] = 2
Suraj Jain

2
@SurajJain如果您想覆盖堆栈:-) C不会检查索引与正在编写的数组的大小,因此可以,a[100000] = 5但是如果幸运的话,如果幸运的话,您只会崩溃应用程序: -)
xanatos

整数a [0]; 手段变量数组(零大小的数组),我怎么能现在给它分配
苏拉杰耆那教

@SurajJain还不清楚“ C不会检查索引与正在编写的数组的大小”的哪一部分?C语言中没有索引检查功能,您可以在数组末尾写入数据,然后使计算机崩溃或覆盖内存中的宝贵位。因此,如果您有一个包含0个元素的数组,则可以在0个元素的结尾之后编写。
xanatos


9

零长度数组的另一个用途是制作可变长度的对象(C99之前的版本)。零长度数组不同柔性阵列 []无0具有。

gcc doc引用:

GNU C中允许使用零长度数组。零长度数组作为结构的最后一个元素(实际上是可变长度对象的标头)非常有用:

 struct line {
   int length;
   char contents[0];
 };
 
 struct line *thisline = (struct line *)
   malloc (sizeof (struct line) + this_length);
 thisline->length = this_length;

在ISO C99中,您将使用灵活的数组成员,其语法和语义略有不同:

  • 灵活的数组成员写为contents [],不带0。
  • 灵活数组成员的类型不完整,因此可能不应用sizeof运算符。

一个真实世界的例子是零长度数组struct kdbus_itemkdbus.h(Linux内核模块)。


2
恕我直言,该标准没有充分的理由禁止使用零长度数组。它可能具有零大小的对象,恰好可以作为结构的成员,并且将它们视为void*算术目的(因此,禁止向零大小的对象添加或减去指针)。尽管灵活数组成员比零大小数组要好得多,但它们也可以充当别名的“联盟”,而无需为后面的内容添加额外的“语法”间接级别(例如,给定struct foo {unsigned char as_bytes[0]; int x,y; float z;}一个可以访问成员的成员)xz...
超级猫

...无需直接说myStruct.asFoo.x,等等。此外,IIRC,C竭尽全力在结构中包含一个柔性数组成员,因此不可能拥有一个包含多个其他已知长度的柔性数组成员的结构内容。
超级猫

@supercat的一个很好的理由是维护有关访问数组外部边界的规则的完整性。作为结构的最后一个成员,C99 flexible数组成员可实现与GCC零大小数组完全相同的效果,但无需在其他规则中添加特殊情况。恕我直言,这是一个改进,sizeof x->contents与在gcc中返回0 相比,这是ISO C中的错误。不是结构成员的零大小数组会带来很多其他问题。
MM 2015年

@MM:如果将两个相等的指针指向零大小的对象定义为产生零(将减去相等的指针指向任何大小的对象),并且将不相等的指针减去为零大小的对象定义为产生,它们将导致什么问题?未指定值?如果标准已指定实现可以允许将包含FAM的结构嵌入到另一个结构中,则前提是该结构中的下一个元素是与FAM具有相同元素类型的数组或以该数组开头的结构,并提供...
supercat

...它将FAM识别为阵列的别名(如果对齐规则会使阵列落在不同的偏移处,则需要进行诊断),这将非常有用。实际上,没有一种方法可以接受指向通用格式结构的指针struct {int n; THING dat[];}并且可以处理静态或自动持续时间的方法,这是一个好方法。
超级猫

6

如果允许结构中的零尺寸数组声明,并且它们的语义使得(1)它们将强制对齐但不分配任何空间,并且(2)索引数组将被视为结构中的已定义行为,则该结构将非常有用。这种情况下,结果指针将与该结构位于同一块内存中。任何C标准都从未允许过这种行为,但是某些较早的编译器允许这样做,然后才成为标准格式,允许编译器使用不完整的带有空括号的数组声明。

通常使用大小为1的数组来实现的struct hack是狡猾的,我认为编译器没有任何要求不要破坏它。例如,我希望,如果编译器看到的int a[1],这将是关于其权利范围内a[i]a[0]。如果有人尝试通过类似的方法来解决struct hack的对齐问题

typedef struct {
  uint32_t大小;
  uint8_t data [4]; //使用四个,以避免填充掉掉结构的大小
}

编译器可能会变得聪明,并假设数组大小确实为四:

; 如写
  foo = myStruct-> data [i];
; 如解释的那样(假设使用little-endian硬件)
  foo =(((*(uint32_t *)myStruct-> data)>>(i << 3))&0xFF;

这样的优化可能是合理的,特别是如果myStruct->data可以通过与相同的操作将其加载到寄存器中时myStruct->size。我对标准中没有什么东西会禁止这种优化一无所知,尽管它当然会破坏任何可能期望访问第四个元素之外的代码。


1
弹性阵列成员已作为结构破解的合法版本添加到C99
MM

该标准确实说,对不同数组成员的访问不会冲突,这将导致无法进行优化。
Ben Voigt 2015年

@BenVoigt:C语言标准未指定同时写入字节和读取包含一个单词的效果,但是99.9%的处理器确实指定写入将成功且该单词将包含新版本或旧版本的字节以及其他字节的未更改内容。如果编译器以此类处理器为目标,那么会有什么冲突?
2015年

@supercat:C语言标准保证了同时写入两个不同的数组元素不会冲突。因此,您的观点(写时读)可以正常运行,这还不够。
Ben Voigt 2015年

@BenVoigt:如果要按一段顺序编写代码,例如写数组元素0、1和2,则不允许将所有四个元素读成一个长整数,修改三个元素,然后写回所有四个元素,但是我认为可以将所有四个读为一个长整数,修改三个,将低16位写为short,将16-23位写为一个字节。你不同意吗?而只需要读取数组元素的代码将被允许简单地将它们读取很长一段时间并使用它。
超级猫
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.