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


127

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

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

#include <stdio.h>

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

осветление

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

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

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

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


51
@AlexanderCorwin: К сожалению, в C ++, с неопределенным поведением, нестандартными расширениями и другими аномалиями, пробовать что-то самостоятельно часто не путь к знаниям.
Бенджамин Линдли

5
@JustinKirk Я тоже попал в ловушку, протестировав и убедившись, что это работает. И из-за критики, которую я получил в своем посте, я узнал, что тестирование и проверка его работоспособности не означает, что он действителен и законен. Так что иногда самотестирование недействительно.
StormByte

2
@JustinKirk, см . Ответ Матье, где вы можете его использовать. Это также может пригодиться в шаблоне, где размер массива является параметром шаблона. Пример в вопросе явно вне контекста.
Марк Рэнсом

2
@JustinKirk: Какая цель []в Python или даже ""в C? Иногда у вас есть функция или макрос, для которых требуется массив, но у вас нет данных для добавления в него.
dan04

15
Что такое «C / C ++»? Это два разных языка
Гонки легкости на орбите

Ответы:


86

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

ISO 9899: 2011 6.7.6.2:

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

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

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


34
«он должен действительно выдать ошибку» - различие между «предупреждениями» и «ошибками» не признается в стандарте (в нем упоминается только «диагностика»), и это единственная ситуация, когда компиляция должна быть остановлена ​​[т.е. реальная разница между предупреждением и ошибкой] обнаруживает #errorдирективу.
Random832

12
К вашему сведению, как правило, стандарты (C или C ++) устанавливают только то, что компиляторы должны разрешать , но не то, что они должны запрещать . В некоторых случаях они будут заявлять, что компилятор должен выдавать «диагностику», но это примерно так же конкретно, как и они. Остальное остается на усмотрение поставщика компилятора. РЕДАКТИРОВАТЬ: То, что сказал Random832.
mcmcc

8
@Lundin "Компилятору не разрешено создавать двоичный файл, содержащий массивы нулевой длины." Стандарт не говорит абсолютно ничего подобного. В нем только говорится, что он должен сгенерировать хотя бы одно диагностическое сообщение, если исходный код содержит массив с постоянным выражением нулевой длины для его размера. Единственное обстоятельство, при котором стандарт запрещает компилятору создавать двоичный файл, - это если он встречает #errorдирективу препроцессора.
Random832

5
@Lundin Создание двоичного файла для всех правильных случаев удовлетворяет # 1, и создание или отсутствие его создания для неправильных случаев не повлияет на это. Для №3 достаточно печати предупреждения. Это поведение не имеет отношения к № 2, поскольку стандарт не определяет поведение этого исходного кода.
Random832

13
@Lundin: Дело в том, что ваше утверждение ошибочно; соответствующие компиляторы имеют право строить бинарный файл , содержащий нулевой длины массивы, до тех пор , в качестве диагностического выдается.
Кейт Томпсон,

85

По стандарту это не допускается.

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

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

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

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


@Lundin: Я не видел здесь VLA, все размеры известны во время компиляции. Термин « гибкий массив» взят из gcc.gnu.org/onlinedocs/gcc-4.1.2/gcc/Zero-Length.html и, int content[];насколько я понимаю, здесь подходит . Поскольку я не слишком разбираюсь в терминах C в этом искусстве ... не могли бы вы подтвердить, что мои рассуждения кажутся правильными?
Matthieu M.

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

Эта идиома также известна под названием «struct hack» , и я встречал больше людей, знакомых с этим названием, чем «структура, дополненная хвостом» (никогда не слышал ее раньше, за исключением, возможно, общей ссылки на заполнение структуры для будущей совместимости с ABI. ) или «гибкий элемент массива», который я впервые услышал в C99.

1
Использование размера массива 1 для взлома структуры позволило бы избежать крика компиляторов, но было только «переносимым», потому что авторы компилятора были достаточно хороши, чтобы признать такое использование стандартом де-факто. Если бы не запрет на массивы нулевого размера, последовательное использование программистами одноэлементных массивов в качестве грязной замены и историческое отношение разработчиков компиляторов к тому, что они должны служить потребностям программистов, даже если этого не требует Стандарт, авторы компиляторов могли бы бы легко и с пользой оптимизированы , foo[x]чтобы foo[0]каждый раз , когда fooбыл массив с одним элементом.
supercat 09

1
@RobertSsupportsMonicaCellio: Это явно показано в ответе, но в конце . Я также заранее подготовил объяснение, чтобы сделать его более понятным с самого начала.
Матье М.,

58

В Стандартных C и C ++ массив нулевого размера не допускается.

Если вы используете GCC, скомпилируйте его с помощью -pedanticoption. Он предупредит , говоря:

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

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


9
В Visual C ++ 2010:error C2466: cannot allocate an array of constant size 0
Марк Рэнсом,

4
-Werror просто превращает все предупреждения в ошибки, что не исправляет некорректное поведение компилятора GCC.
Lundin

C ++ Builder 2009 также правильно выдает ошибку:[BCC32 Error] test.c(3): E2021 Array must have at least one element
Lundin

1
Вместо этого -pedantic -Werrorвы могли бы просто сделать-pedantic-errors
Стефан Доллберг

3
Массив нулевого размера - это не совсем то же самое, что и массив нулевого размера std::array. (Кроме того: я помню, но не могу найти источник, в котором VLA были рассмотрены и явно отклонены из-за того, что они находятся на C ++.)

27

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

char someCondition[ condition ];

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

char someCondition[ 2 * condition - 1 ];

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


Это интересный способ его использования.
Alex Koay

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

Почему не просто:#if condition \n #error whatever \n #endif
Jerfov2

1
@ Jerfov2, потому что условие может быть неизвестно во время предварительной обработки, только во время компиляции
rmeador

9

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

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

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

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

и

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

чтобы ты мог

int arr[0] = { 1 };

и бум :-)


Могу ли я сделать , как int a[0], то a[0] = 1 a[1] = 2??
Сурадж Джайн

2
@SurajJain Если вы хотите перезаписать свой стек :-) C не проверяет индекс и размер массива, который вы пишете, так что вы можете, a[100000] = 5но если вам повезет, вы просто разобьете свое приложение, если вам повезет: -)
xanatos

Int a [0]; означает массив переменных (массив нулевого размера), как мне теперь назначить его?
Сурадж Джайн

@SurajJain Какая часть «C не проверяет индекс по сравнению с размером массива, который вы пишете» неясна? В C нет проверки индекса, вы можете писать после конца массива и вывести компьютер из строя или перезаписать драгоценные биты вашей памяти. Итак, если у вас есть массив из 0 элементов, вы можете писать после конца 0 элементов.
xanatos


9

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

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

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


2
IMHO, у Стандарта не было веских причин запрещать массивы нулевой длины; он может иметь объекты нулевого размера, которые прекрасно подходят как члены структуры, и рассматривать их как void*арифметические (поэтому добавление или вычитание указателей на объекты нулевого размера было бы запрещено). Хотя гибкие элементы массива в основном лучше массивов нулевого размера, они также могут действовать как своего рода «объединение» для псевдонимов, не добавляя дополнительного уровня «синтаксического» косвенного обращения к тому, что следует (например, если struct foo {unsigned char as_bytes[0]; int x,y; float z;}можно получить доступ к элементам x.. z...
supercat

... напрямую, без необходимости говорить, например myStruct.asFoo.x, и т. д. Кроме того, IIRC, C кричит при любых усилиях по включению гибкого элемента массива в структуру, что делает невозможным наличие структуры, которая включает в себя несколько других элементов гибкого массива известной длины содержание.
supercat

@supercat - веская причина - сохранить целостность правила доступа за пределы массива. В качестве последнего члена структуры элемент гибкого массива C99 обеспечивает точно такой же эффект, что и массив нулевого размера GCC, но без необходимости добавлять особые случаи в другие правила. IMHO, это улучшение, которое sizeof x->contentsявляется ошибкой в ​​ISO C, а не возвращением 0 в gcc. Массивы нулевого размера, не являющиеся членами структуры, создают множество других проблем.
MM

@MM: Какие проблемы они могли бы вызвать, если бы вычитание двух равных указателей на объект нулевого размера было определено как дающее ноль (как и вычитание равных указателей на любой размер объекта), а вычитание неравных указателей на объекты нулевого размера было определено как дающее Неуказанное значение? Если в Стандарте указано, что реализация может разрешить встраивание структуры, содержащей FAM, в другую структуру при условии, что следующий элемент в последней структуре является либо массивом с тем же типом элемента, что и FAM, либо структурой, начинающейся с такого массива , и при условии, что ...
supercat 01

... он распознает FAM как псевдоним массива (если правила выравнивания заставят массивы приземляться на разных смещениях, потребуется диагностика), что было бы очень полезно. На самом деле нет хорошего способа иметь метод, который принимает указатели на структуры общего формата struct {int n; THING dat[];}и может работать с объектами статической или автоматической продолжительности.
supercat 01

6

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

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

typedef struct {
  uint32_t size;
  uint8_t data [4]; // Используйте четыре, чтобы заполнение не уменьшало размер структуры
}

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

; Как написано
  foo = myStruct-> data [я];
; В интерпретации (при условии, что аппаратное обеспечение с прямым порядком байтов)
  foo = ((* (uint32_t *) myStruct-> data) >> (i << 3)) & 0xFF;

Такая оптимизация может быть разумной, особенно если ее myStruct->dataможно загрузить в регистр в той же операции, что и myStruct->size. Я не знаю ничего в стандарте, который запрещал бы такую ​​оптимизацию, хотя, конечно, это нарушило бы любой код, который мог ожидать доступа к материалам за пределами четвертого элемента.


1
Элемент гибкого массива был добавлен в C99 как легитимная версия взлома структуры
MM

Стандарт действительно говорит, что доступ к разным элементам массива не конфликтует, что делает эту оптимизацию невозможной.
Бен Войт

@BenVoigt: Стандарт языка C не определяет эффект одновременной записи байта и чтения содержащегося слова, но 99,9% процессоров указывают, что запись будет успешной, и слово будет содержать либо новую, либо старую версию byte вместе с неизмененным содержимым остальных байтов. Если компилятор нацелен на такие процессоры, в чем будет конфликт?
supercat

@supercat: Стандарт языка C гарантирует, что одновременная запись в два разных элемента массива не будет конфликтовать. Таким образом, вашего аргумента о том, что (читать, пока писать) работает нормально, недостаточно.
Бен Фойгт,

@BenVoigt: Если бы кусок кода, например, записывал в элементы массива 0, 1 и 2 в некоторой последовательности, было бы запрещено читать все четыре элемента в длинный, изменять три и записывать обратно все четыре, но я думаю, можно было бы прочитать все четыре в длинный, изменить три, записать младшие 16 бит как короткий, а биты 16-23 как байт. Вы бы не согласились с этим? А коду, который должен был читать только элементы массива, будет разрешено просто читать их в long и использовать это.
supercat
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.