如果我在C/C++中定义0大小的数组会发生什么?

Ale*_*oay 120 c c++ arrays

只是好奇,如果我int array[0];在代码中定义零长度数组会发生什么?海湾合作委员会根本没有抱怨.

示例程序

#include <stdio.h>

int main() {
    int arr[0];
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

澄清

我实际上是在试图弄清楚零长度数组是否以这种方式初始化,而不是像Darhazer的注释中那样指向可变长度,是否优化了.

这是因为我必须释放一些代码,所以我想弄清楚我是否必须处理SIZE定义为的情况0,这在一些静态定义的代码中会发生int array[SIZE];

我真的很惊讶海湾合作委员会没有抱怨,这导致了我的问题.根据我收到的答案,我认为缺少警告主要是因为支持未使用新[]语法更新的旧代码.

因为我主要是想知道这个错误,所以我将Lundin的答案标记为正确(Nawaz是第一个,但它并不完整) - 其他人指出它实际用于尾部填充结构,虽然相关,但不是正是我想要的.

Mat*_* M. 79

通常情况下,不允许这样做.

然而,在C中使用灵活阵列是当前的做法.

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

示范:

struct Array {
  size_t size;
  int content[];
};
Run Code Online (Sandbox Code Playgroud)

这个想法是你会分配它:

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;
  }
}
Run Code Online (Sandbox Code Playgroud)

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

Array a = { 3, { 1, 2, 3 } };
Run Code Online (Sandbox Code Playgroud)

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

然而,这种语法最近才在C99中标准化(并保证了效果).在需要恒定尺寸之前.

  • 1 是可移动的方式,虽然它很奇怪
  • 0 更好地表明了意图,但就标准而言并不合法,并且作为某些编译器(包括gcc)的扩展支持

然而,尾部填充练习依赖于存储可用(小心malloc)的事实,因此通常不适合堆栈使用.


Lun*_*din 79

数组的大小不能为零.

ISO 9899:2011 6.7.6.2:

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

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

gcc -std=c99 -pedantic 对非VLA案件发出警告.

  • "它实际上必须给出一个错误" - 标准中没有认识到"警告"和"错误"之间的区别(它只提到"诊断"),而且编译必须停止的唯一情况[即现实世界的差异]在警告和错误之间]正在遇到`#error`指令. (32认同)
  • @Lundin:关键是你的陈述是错误的; 只要发出诊断,就允许符合编译器*构建包含零长度数组的二进制文件. (12认同)
  • 仅供参考,作为一般规则,(C或C++)标准仅说明编译器必须_allow_,而不是它们必须_disallow_.在某些情况下,他们会声明编译器应该发出"诊断",但这与他们得到的具体相同.其余的留给编译器供应商.编辑:Random832也说了什么. (11认同)
  • @Lundin"不允许编译器构建包含零长度数组的二进制文件." 标准说_绝对不是那种.它只表示当给定的源代码包含一个其大小为零长度常量表达式的数组时,它必须至少生成一条诊断消息.标准禁止编译器构建二进制文件的唯一情况是它遇到`#error`预处理器指令. (8认同)
  • @Lundin为所有正确的情况生成二进制文件满足#1,并且生成或不生成一个用于不正确的情况不会影响它.打印警告就足够了#3.此行为与#2无关,因为标准未定义此源代码的行为. (5认同)
  • 只是注意,GCC的'-pedantic`标志*从不*产生错误,只有警告(假设`-Werror`没有打开),这就是`-pedantic-errors`的用途. (4认同)
  • 你是在哪里拿到的?C996.7.5.2§5:*如果大小是一个表达式,它不是一个整数常量表达式[...]每次评估时它的值应大于零.* (2认同)

Naw*_*waz 57

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

如果您正在使用GCC,请使用-pedantic选项进行编译.它会发出警告,说:

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

在C++的情况下,它给出了类似的警告.

  • 在Visual C++ 2010中:`错误C2466:无法分配常量大小为0的数组 (9认同)
  • -Werror简单地将所有警告转换为错误,这不会修复GCC编译器的错误行为. (4认同)
  • 零大小的数组与零大小的`std :: array`不完全相同.(旁白:我记得但是找不到VGA被认为是明确拒绝使用C++的来源.) (3认同)

Jam*_*nze 24

这完全是非法的,而且一直都是,但很多编译器忽略了发出错误的信号.我不确定你为什么要这样做.我所知道的一个用途是从布尔值触发编译时错误:

char someCondition[ condition ];
Run Code Online (Sandbox Code Playgroud)

如果condition是false,那么我得到一个编译时错误.但是,因为编译器允许这样做,我已经开始使用:

char someCondition[ 2 * condition - 1 ];
Run Code Online (Sandbox Code Playgroud)

这给出了1或-1的大小,我从来没有找到一个接受大小为-1的编译器.

  • 我认为这是元编程中的常见技巧.如果`STATIC_ASSERT`的实现使用它,我不会感到惊讶. (10认同)

xan*_*tos 8

我将补充说,在这个论点上有一整页关于gcc的在线文档.

一些引言:

GNU C中允许零长度数组.

在ISO C90中,您必须为内容提供长度为1的内容

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

所以你可以

int arr[0] = { 1 };
Run Code Online (Sandbox Code Playgroud)

和繁荣:-)

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

Duk*_*uke 8

零长度数组的另一个用途是用于制作可变长度的对象(前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;
Run Code Online (Sandbox Code Playgroud)

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

  • 灵活的数组成员被写为内容[]而没有0.
  • 灵活的数组成员具有不完整的类型,因此可能不会应用sizeof运算符.

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

  • 恕我直言,标准没有充分的理由禁止零长度数组;它可以将零大小的对象作为结构的成员,并出于算术目的将它们视为“void*”(因此禁止添加或减去指向零大小对象的指针)。虽然灵活的数组成员大多比零大小的数组更好,但它们也可以作为一种“联合”来为事物别名,而无需为后面的内容添加额外的“语法”间接级别(例如,给定 `struct foo {unsigned char as_bytes[0]; int x,y; float z;}` 可以访问成员`x`..`z`... (2认同)

sup*_*cat 6

如果允许结构中的零大小数组声明将是有用的,并且如果语义是这样的:(1)它们将强制对齐但是否则不分配任何空间,并且(2)索引数组将被认为是在结果指针与结构存在于同一内存块中的情况.任何C标准都不允许这样的行为,但是一些较旧的编译器在它成为编译器的标准之前允许它允许带有空括号的不完整数组声明.

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

typedef struct {
  uint32_t size;
  uint8_t data[4];  // Use four, to avoid having padding throw off the size of the struct
}

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

; As written
  foo = myStruct->data[i];
; As interpreted (assuming little-endian hardware)
  foo = ((*(uint32_t*)myStruct->data) >> (i << 3)) & 0xFF;

这样的优化可能是合理的,特别是如果myStruct->data可以在相同的操作中加载到寄存器中myStruct->size.我在禁止这种优化的标准中一无所知,但当然它会破坏任何可能期望访问超出第四个元素的代码的代码.