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

只是好奇,如果我在代码中定义一个零长度的数组 int array[0];会发生什么。

样本程序

#include <stdio.h>


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

澄清一下

我实际上是想弄清楚这样初始化的零长度数组是否被优化了而不是像达哈泽评论中的变量长度那样被指向。

这是因为我必须将一些代码发布到外部,所以我试图弄清楚是否必须处理 SIZE被定义为 0的情况,这种情况发生在一些具有静态定义的 int array[SIZE];的代码中

我很惊讶海湾合作委员会竟然没有抱怨,这就引出了我的问题。从我收到的答案来看,我相信缺少警告的主要原因是支持没有用新的[]语法更新的旧代码。

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

93382 次浏览

在标准 C 和 C + + 中,允许使用零大小的数组 没有

如果你使用 GCC,用 -pedantic选项编译它,它会给出 警告,说:

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

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

我要补充的是,关于这个参数,gcc 的在线文档有一个 整页

一些名言:

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

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

还有

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

所以你可以

int arr[0] = { 1 };

然后砰的一声: -)

根据标准,这是不允许的。

然而,目前 C 编译器的做法是将这些声明视为 灵活的数组成员(< a href = “ https://en.wikipedia.org/wiki/Flex _ array _ member”rel = “ noReferrer”> FAM )声明:

C996.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标准的出版)或者 Struct Hack(感谢 Joe Wreschnig 指出)。

然而,这种语法是标准化的(和效果保证)只是最近在 C99。之前,一个常量大小是必要的。

  • 1是便携式的方式去,虽然它是相当奇怪的。
  • 0在表示意图方面做得更好,但就标准而言并不合法,而且一些编译器(包括 gcc)支持它作为扩展。

然而,尾部填充实践依赖于存储是可用的这一事实(小心 malloc) ,所以 不合适通常也可以堆栈使用。

这完全是非法的,一直都是,但是很多编译器 我不知道你为什么要这么做。 我知道的一种用法是从一个布尔值触发一个编译时错误:

char someCondition[ condition ];

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

char someCondition[ 2 * condition - 1 ];

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

数组不能大小为零。

ISO 9899:20116.7.6.2:

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

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

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

如果结构中的零大小数组声明被允许,那么它们将是有用的,如果语义是这样的: (1)它们将强制对齐,但不分配任何空间,(2)索引数组将被认为是已定义的行为,在这种情况下,结果指针将与结构在同一块内存中。这种行为从未被任何 C 标准所允许,但是一些较老的编译器在它成为编译器允许带空括号的不完整数组声明的标准之前就允许了。

通常使用大小为1的数组来实现 struct hack,这种做法是不可靠的,我认为没有任何要求要求编译器避免破坏它。例如,我希望如果编译器看到 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
}

编译器可能会变聪明,假设数组的大小真的是4:

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

这种优化可能是合理的,特别是如果 myStruct->data可以以与 myStruct->size相同的操作加载到寄存器中。我不知道在标准中禁止这样的优化,虽然它会破坏任何代码,可能期望访问第四个元素以外的东西。

零长度数组的另一个用途是创建可变长度的对象(C99之前)。零长度数组是来自 柔性阵列与众不同,它有[]而没有0。

引自 GCC 文件:

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中,您将使用一个灵活的数组成员,它在语法和语义上略有不同:

  • 灵活的数组成员以内容[]的形式编写,不使用0。
  • 柔性阵列成员类型不完全,因此可能不适用 sizeof 算子。

一个真实的例子是 Kdbush(一个 Linux 内核模块)中的 struct kdbus_item零长度数组。

毫无疑问,你不能按标准使用零大小的数组,但实际上每个最流行的编译器都会让你这么做。所以我会试着解释为什么会很糟糕

#include <cstdio>


int main() {
struct A {
A() {
printf("A()\n");
}
~A() {
printf("~A()\n");
}
int empty[0];
};
A vals[3];
}

我就像人类一样,期待着这样的结果:

A()
A()
A()
~A()
~A()
~A()

Clang 打印了这个:

A()
~A()

海湾合作委员会报道:

A()
A()
A()

这非常奇怪,所以如果可以的话,在 C + + 中不使用空数组是一个很好的理由。

GNU C中还有一个扩展,它允许你创建零长度的数组 C 调,但是正如我所理解的那样,在结构之前至少应该有一个成员,否则如果你使用 C + + ,你会得到非常奇怪的例子。