Что произойдет, если я определяю массив 0-размера в C/С++?

Просто любопытно, что на самом деле происходит, если я определяю массив с нулевой длиной int array[0]; в коде? GCC вообще не жалуется.

Пример программы

#include <stdio.h>

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

Разъяснение

Я на самом деле пытаюсь выяснить, были ли инициализированы эти массивы нулевой длины, вместо того, чтобы указывать на переменную длину в комментариях Darhazer, оптимизированы или нет.

Это связано с тем, что я должен выпустить какой-то код в wild, поэтому я пытаюсь выяснить, должен ли я обрабатывать случаи, когда SIZE определяется как 0, что происходит в некотором коде с статически определено int array[SIZE];

Я был действительно удивлен, что GCC не жалуется, что привело к моему вопросу. Из полученных ответов я считаю, что отсутствие предупреждения во многом связано с поддержкой старого кода, который не был обновлен новым синтаксисом [].

Поскольку я в основном интересовался ошибкой, я отмечаю ответ Лундина как правильный (сначала был Nawaz, но он был не таким полным), остальные указывали на его фактическое использование для хвостовых конструкций, в то время как соответствующие, не совсем то, что я искал.

Ответ 1

Массив не может иметь нулевой размер.

ISO 9899: 2011 6.7.6.2:

Если выражение является константным выражением, оно должно иметь значение больше нуля.

Вышеприведенный текст верен как для простого массива (параграф 1). Для VLA (массив переменной длины) поведение undefined, если значение выражения меньше или равно нулю (параграф 5). Это нормативный текст в стандарте C. Компилятору не разрешено реализовать его по-разному.

gcc -std=c99 -pedantic дает предупреждение для случая, отличного от VLA.

Ответ 2

Обычно это не разрешено.

Однако для использования гибких массивов в C была применена текущая практика.

C99 6.7.2.1, §16. В качестве частного случая последний элемент структуры с более чем одним именованным элементом может иметь неполный тип массива; это называется гибким элементом массива.

Демонстрация:

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) или структурного взлома (спасибо Джо Врешнигу за указание его).

Однако этот синтаксис был стандартизирован (и гарантированно эффект) только в последнее время на C99. Прежде чем понадобится постоянный размер.

  • 1 был переносным способом, хотя это было довольно странно.
  • 0 было лучше указывать намерение, но не было законным в отношении стандарта и поддерживалось как расширение некоторыми компиляторами (включая gcc)

Однако практика заполнения хвоста основана на том, что хранилище доступно (осторожно malloc), поэтому не подходит для общего использования стека.

Ответ 3

В стандартном C и С++ массив нулевого размера не разрешен.

Если вы используете GCC, скомпилируйте его с опцией -pedantic. Он даст предупреждение, говоря:

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

В случае С++ он дает аналогичное предупреждение.

Ответ 4

Это совершенно незаконно и всегда было, но много компиляторов пренебрегать сигналом ошибки. Я не уверен, почему вы хотите это сделать. Единственное, что я знаю, это вызвать ошибку времени компиляции из булева:

char someCondition[ condition ];

Если condition является ложным, тогда я получаю ошибку времени компиляции. Потому как компиляторы действительно позволяют это, однако, я принял к использованию:

char someCondition[ 2 * condition - 1 ];

Это дает размер 1 или -1, и я никогда не нашел компилятор который примет размер -1.

Ответ 5

Я добавлю, что есть целая страница онлайн-документации gcc по этому аргументу.

Некоторые цитаты:

В GNU C. допустимы массивы нулевой длины.

В ISO C90 вам нужно будет предоставить содержимое длиной 1

и

Варианты GCC до 3.0 допускали, чтобы массивы нулевой длины были статически инициализированы, как если бы они были гибкими массивами. В дополнение к тем случаям, которые были полезны, он также допускал инициализацию в ситуациях, которые могли бы повредить более поздние данные

чтобы вы могли

int arr[0] = { 1 };

и стрела: -)

Ответ 6

Другое использование массивов с нулевой длиной - это создание объекта переменной длины (pre-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 вы должны использовать гибкий элемент массива, который немного отличается в синтаксисе и семантике:

  • Элементы гибкого массива записываются как содержимое [] без 0.
  • Элементы гибкого массива имеют неполный тип, поэтому оператор sizeof не может быть применен.

Пример в реальном мире - это массивы нулевой длины struct kdbus_item в kdbus.h (модуль ядра Linux).

Ответ 7

Объявления массива массивов нулевого размера внутри структур были бы полезны, если бы они были разрешены, и если семантика была такова, что (1) они будут принудительно выравнивать, но в противном случае не выделять какое-либо пространство, и (2) индексирование массива будет считаться определенным поведение в случае, когда результирующий указатель будет находиться в одном блоке памяти, таком как структура. Такое поведение никогда не разрешалось ни одним стандартом С, но некоторые старые компиляторы разрешали его, прежде чем он стал стандартным для компиляторов, чтобы разрешить объявления неполного массива с пустыми скобками.

Структурный хак, который обычно реализуется с использованием массива размером 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. Я ничего не знаю в стандарте, который запретил бы такую ​​оптимизацию, хотя, конечно, он сломал бы любой код, который мог бы ожидать доступа к материалам за четвертым элементом.