TL; DR
Короткий ответ заключается в том, что эта конкретная конструкция не разрешена стандартом C11 или любым другим стандартом C, восходящим к ANSI C (1989), - но она принята как расширение компилятора многими, хотя и не всеми, современные компиляторы Си. В конкретном случае GCC вам не нужно использовать -pedantic
(или -pedantic-errors
), что может привести к строгой интерпретации стандарта C. (Другой обходной путь описан ниже.)
Примечание : Хотя вы можете произносить -pedantic
с помощью W
, это не похоже на многие опции -W
, поскольку оно не только добавляет предупреждающие сообщения: что делает:
Выпускать все предупреждения, требуемые строгими стандартами ISO C и ISO C ++; отклонить все программы, которые используют запрещенные расширения, и некоторые другие программы, которые не соответствуют ISO C и ISO C ++.
Обходные
По-видимому, невозможно устранить эту ошибку с помощью GCC #pragma
, или, по крайней мере, те, которые я пробовал, не имели никакого эффекта. Можно подавить его для одного объявления, используя расширение __extension__
, но это, кажется, просто обменивает одну несовместимость на другую, поскольку тогда вам нужно будет найти способ удалить (или развернуть макрос) __extension__
для другие компиляторы.
Цитирование Руководство GCC :
-pedantic
и другие опции вызывают предупреждения для многих расширений GNU C. Вы можете предотвратить такие предупреждения в одном выражении, написав __extension__
перед выражением. __extension__
не имеет никакого влияния, кроме этого.
На версиях GCC, которые мне пригодились, следующее работало без предупреждений даже с -pedantic
:
__extension__ static struct example_s { int i; } example[];
Вероятно, вам лучше всего просто удалить -pedantic
из опций сборки. Я не верю, что -pedantic
действительно так полезен; Стоит прочитать, что в руководстве GCC сказано об этом . В любом случае, он делает свою работу здесь: задокументированное намерение состоит в том, чтобы запретить расширения, и это то, что он делает.
Язык-адвокатская практика
Язык-адвокат оправдывает вышесказанное, принимая во внимание некоторые длинные нити комментариев:
Определения
Внешнее объявление - это объявление в области видимости файла, вне определения любой функции. Это не должно быть перепутано с внешней связью , которая является совершенно другим использованием этого слова. Стандарт называет внешние объявления «внешними» именно потому, что они находятся вне каких-либо определений функций.
Единицей перевода, таким образом, является последовательность external-объявление . См. Раздел 6.9.
Если внешнее объявление также является определением, т. Е. Является либо объявлением функции с телом, либо объявлением объекта с инициализатором, то оно называется внешним определением .
Тип является неполным в той точке программы, где нет «достаточной информации для определения размера объектов этого типа» (& sect; 6.2. 5p1), который включает «тип массива неизвестного размера» (& sect; 6.2.5p22) (Я вернусь к этому абзацу позже.) (Существуют и другие способы, чтобы тип был неполным, но они здесь не актуальны.)
Внешним объявлением объекта является предварительное определение (& sect; 6.9.2), если оно не является определением и помечено static
или имеет нет спецификатора класса хранения. (Другими словами, extern
декларации не являются предварительными.)
Что касается предварительных определений, интересно то, что они могут стать определениями. Несколько объявлений могут быть объединены с одним определением, и вы также можете иметь несколько объявлений (в единице перевода) без какого-либо определения (в этой единице перевода), при условии, что символ имеет внешнюю связь и что есть определение в некоторой другой единице перевода , Но в конкретном случае, когда definition и all объявлений символа не являются предварительными, компилятор автоматически вставит определение.
Короче говоря, если символ имеет какое-либо (внешнее) объявление с явным extern
, он не может претендовать на автоматическое определение (поскольку явно помеченное объявление не является предварительным).
Краткий обход: важность связи первой декларации
Еще одна любопытная особенность: если объявление first для объекта явно не помечено static
, то никакое объявление для этого объекта не может быть помечено static
, поскольку считается объявление без класса хранения. иметь внешнюю связь, если идентификатор не был объявлен как имеющий внутреннюю связь (& sect; 6.2.2p5), и идентификатор не может быть объявлен как имеющий внутреннюю связь, если он уже был объявлен как имеющий внешнюю связь (& sect; 6.2.2p7) , Однако, если первое объявление для объекта явно static
, то последующие объявления не влияют на его связь. (& Раздел; 6.2.2p4)
.
Что все это значило для ранних разработчиков
Предположим, вы пишете компилятор на чрезвычайно ограниченном по ресурсам процессоре (по современным стандартам), что было в основном для всех ранних разработчиков компиляторов. Когда вы видите внешнюю декларацию для символа, вам нужно либо дать ему адрес в текущей единице перевода (для символов с внутренней связью), либо добавить его в список символов, который вы собираетесь разрешить компоновщику обрабатывать (для символов с внешней связью). Поскольку компоновщик будет назначать адреса внешним символам, вам еще не нужно знать, каков их размер. Но для символов, которые вы собираетесь обрабатывать самостоятельно, вам нужно сразу дать им адрес (в пределах сегмента данных), чтобы вы могли генерировать машинный код, ссылающийся на данные, а это значит, что вам нужно знать, какой размер они имеют. объекты.
Как отмечалось выше, вы можете определить, является ли символ внутренним или внешним, когда вы впервые видите его объявление, и он должен быть объявлен до его использования. Таким образом, к тому моменту, когда вам нужно будет генерировать код, используя символ, вы можете знать, следует ли генерировать код, ссылающийся на определенное известное смещение в сегменте данных, или выдавать перемещаемую ссылку, которая будет заполнена компоновщиком позже.
Но есть небольшая проблема: что, если первое объявление будет неполным ? Это не проблема для внешних связанных символов, но для внутренних связанных символов это не позволяет вам распределить его по диапазону адресов, поскольку вы не знаете, насколько он велик. И к тому времени, когда вы узнаете, вы, возможно, должны были испустить код, используя его. Чтобы избежать этой проблемы, необходимо, чтобы первое объявление внутренне связанного символа было завершено. Другими словами, не может быть предварительного объявления о неполном символе, о чем говорит стандарт в разделе 6.9.2p3:
Если объявление идентификатора для объекта является предварительным определением и имеет внутреннюю связь, объявленный тип не должен быть неполным типом.
Немного палеоцибернетики
Это не новое требование. Он присутствовал с точно такой же формулировкой в разделе 3.7.2 документа C89. И эта проблема возникала несколько раз в течение многих лет в группах comp.lang.c
и comp.std.c
Usenix, не привлекая однозначного объяснения. То, что я привел выше, является моим лучшим предположением в сочетании с подсказками из следующих обсуждений:
И это также всплывало несколько раз в Stackoverflow:
Последнее сомнение
Хотя никто из вышеупомянутых дебатов не упоминал об этом, фактическая формулировка раздела 6.2.5p22:
Тип массива неизвестного размера является неполным типом. Для идентификатора этого типа он завершается указанием размера в последующем объявлении (с внутренней или внешней связью).
Это, безусловно, противоречит разделу 6.9.2p3, поскольку предполагает «более позднюю декларацию с внутренней связью», которая не допускается запретом на предварительные определения с внутренней связью и неполным типом. Эта формулировка также дословно содержится в C89 (в разделе 3.1.2.5), поэтому, если это внутреннее противоречие, оно было в стандарте в течение 30 лет, и я не смог найти отчет о дефектах, в котором упоминается ( хотя DR010 и DR016 парят по краям).
Примечание:
Для C89 я использовал этот файл, сохраненный в Wayback Machine , но у меня нет никаких доказательств того, что это правильно. (В архиве есть другие экземпляры этого файла, поэтому есть некоторые подтверждения.) Когда ISO фактически выпустил C90, разделы были перенумерованы. См. этот информационный бюллетень , вежливую википедию.