Объявление статического массива перед его определением при компиляции с GCC - PullRequest
3 голосов
/ 11 марта 2019

Компилятор GCC и компиляторы Clang ведут себя по-разному, где Clang позволяет объявлять переменную static до ее определения, в то время как компилятор GCC обрабатывает объявление (или «предварительное определение») как определение.

Я полагаю, что это ошибка в GCC, но жалоба на нее и открытие отчета об ошибке не решит проблему того, что мне нужен код для компиляции в GCC сегодня (или вчера) ...

Вот быстрый пример:

static struct example_s { int i; } example[];

int main(void) {
  fprintf(stderr, "Number: %d\n", example[0].i);
  return 0;
}

static struct example_s example[] = {{1}, {2}, {3}};

С помощью компилятора Clang программа компилирует и печатает:

Number: 1

Однако, с GCC код не скомпилируется, и я получаю следующие ошибки (игнорируем номера строк):

src/main2.c:26:36: error: array size missing in ‘example’
 static struct example_s { int i; } example[];
                                    ^~~~~~~
src/main2.c:33:25: error: conflicting types for ‘example’
 static struct example_s example[256] = {{1}, {2}, {3}};
                         ^~~~~~~
src/main2.c:26:36: note: previous declaration of ‘example’ was here
 static struct example_s { int i; } example[];

Это ошибка GCC или ошибка Clang? кто знает. Может быть, если вы в одной из команд, вы можете решить.

Что касается меня, статическое объявление, предшествующее статическому определению, должно быть (AFAIK) действительным C («предварительное определение», согласно разделу 6.9.2 стандарта C11) ... поэтому я предполагаю, что есть некоторые расширение в GCC, которое все портит.

Любой способ добавить pragma или другую директиву, чтобы убедиться, что GCC рассматривает декларацию как декларацию?

Ответы [ 3 ]

3 голосов
/ 11 марта 2019

В проекте C11 есть это в §6.9.2 Определения внешнего объекта:

3 Если объявление идентификатора для объекта является предварительным определением и имеет внутреннюю связь, объявленный тип не долженбыть неполным типом

Я прочитал это как то, что первая строка в вашем коде, имеющая массив неопределенной длины, не может быть правильным предварительным определением.Не уверен, что это станет тогда, но это как бы объясняет первое сообщение GCC.

1 голос
/ 12 марта 2019

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 сказано об этом . В любом случае, он делает свою работу здесь: задокументированное намерение состоит в том, чтобы запретить расширения, и это то, что он делает.

Язык-адвокатская практика

Язык-адвокат оправдывает вышесказанное, принимая во внимание некоторые длинные нити комментариев:

Определения

  1. Внешнее объявление - это объявление в области видимости файла, вне определения любой функции. Это не должно быть перепутано с внешней связью , которая является совершенно другим использованием этого слова. Стандарт называет внешние объявления «внешними» именно потому, что они находятся вне каких-либо определений функций.

    Единицей перевода, таким образом, является последовательность external-объявление . См. Раздел 6.9.

    Если внешнее объявление также является определением, т. Е. Является либо объявлением функции с телом, либо объявлением объекта с инициализатором, то оно называется внешним определением .

  2. Тип является неполным в той точке программы, где нет «достаточной информации для определения размера объектов этого типа» (& sect; 6.2. 5p1), который включает «тип массива неизвестного размера» (& sect; 6.2.5p22) (Я вернусь к этому абзацу позже.) (Существуют и другие способы, чтобы тип был неполным, но они здесь не актуальны.)

  3. Внешним объявлением объекта является предварительное определение (& 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, разделы были перенумерованы. См. этот информационный бюллетень , вежливую википедию.

1 голос
/ 11 марта 2019

Редактировать : Очевидно, gcc выдавал ошибку из-за флага -Wpedantic, который (по какой-то неясной причине) добавлял ошибки в дополнение к предупреждениям (см .: godbolt.org и уберите флаг для компиляции).

¯\_(ツ)_/¯ 

Возможный (хотя и не DRY) ответ состоит в добавлении длины массива к исходному объявлению (создание полного типа с предварительным объявлением)в том, что касается C11) ... то есть:

static struct example_s { int i; } example[3];

int main(void) {
  fprintf(stderr, "Number: %d\n", example[0].i);
  return 0;
}

static struct example_s example[3] = {{1}, {2}, {3}};

Это очень раздражает, поскольку в нем возникают проблемы с техническим обслуживанием, но это временное решение, которое работает.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...