繁体   English   中英

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

[英]What happens if I define a 0-size array in C/C++?

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

示例程序

#include <stdio.h>

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

澄清

我实际上是想弄清楚零长度数组是否以这种方式初始化,而不是像 Darhazer 评论中的可变长度那样指向,是否被优化。

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

我实际上很惊讶 GCC 没有抱怨,这导致了我的问题。 从我收到的答案来看,我认为没有警告主要是由于支持尚未使用新 [] 语法更新的旧代码。

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

数组的大小不能为零。

ISO 9899:2011 6.7.6.2:

如果表达式是一个常量表达式,它的值应该大于零。

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

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

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

然而,目前 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 标准的发布)或struct hack (感谢 Joe Wreschnig 指出)。

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

  • 1是便携式的方式,虽然它很奇怪。
  • 0更能表明意图,但就标准而言是不合法的,并且被一些编译器(包括 gcc)支持作为扩展。

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

在标准C和C ++,零大小的数组不允许..

如果您使用 GCC,请使用-pedantic选项编译它。 它会发出警告,说:

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

在 C++ 的情况下,它会给出类似的警告。

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

char someCondition[ condition ];

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

char someCondition[ 2 * condition - 1 ];

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

零长度数组的另一个用途是制作可变长度对象(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 运算符。

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

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

一些引用:

GNU C 中允许零长度数组。

在 ISO C90 中,您必须为内容指定长度为 1

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

所以你可以

int arr[0] = { 1 };

和繁荣:-)

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

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

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相同的操作中加载到寄存器中。 我不知道标准中的任何内容会禁止这种优化,尽管它当然会破坏任何可能期望访问第四个元素之外的内容的代码。

当然,按照标准,您不能拥有零大小的数组,但实际上每个最流行的编译器都可以让您这样做。 所以我会试着解释为什么它会很糟糕

#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()

GCC 打印:

A()
A()
A()

这很奇怪,所以如果可以的话,最好不要在 C++ 中使用空数组。

GNU C 中还有一个扩展,它使您可以在 C 中创建零长度数组,但据我所知,结构中应该至少有一个成员,否则如果您使用 C++,您将得到上述非常奇怪的示例.

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM