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

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

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

#include <stdio.h>

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

осветление

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

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

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

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

8 ответов

Решение

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

ISO 9899: 2011 6.7.6.2:

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

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

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

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

Тем не менее, в настоящее время в Си практикуется использование гибких массивов.

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) так что вообще не подходит для использования в стеке.

В стандартах 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 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).

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

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

Массивы нулевой длины разрешены в GNU C.

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

а также

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

чтобы ты мог

int arr[0] = { 1 };

и бум:-)

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

Хак со структурой, который обычно реализуется с использованием массива размера 1, является хитрым, и я не думаю, что есть какие-то требования, которые компиляторы должны воздерживаться от его взлома. Например, я ожидаю, что если компилятор увидит int a[1]было бы в пределах его прав a[i] как a[0], Если кто-то пытается обойти проблемы выравнивания структуры взломать что-то вроде

typedef struct {
  размер uint32_t;
  данные uint8_t [4];  // Используем четыре, чтобы избежать смещения отступа от размера структуры
}

компилятор может стать умным и предположить, что размер массива действительно равен четырем:

; Как написано
  foo = myStruct->data[i];; Как истолковано (при условии, что в порядке аппаратного обеспечения)
  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++.

Другие вопросы по тегам