WedX - журнал о программировании и компьютерных науках

Предварительные определения в C и связывание

Рассмотрим программу на C, состоящую из двух файлов:

f1.c:

int x;

f2.c:

int x=2;

Мое прочтение параграфа 6.9.2 стандарта C99 состоит в том, что эту программу следует отвергнуть. В моей интерпретации 6.9.2 переменная x предварительно определена в f1.c, но это предварительное определение становится фактическим определением в конце единицы перевода и (на мой взгляд) должно вести себя так, как если бы f1.c содержало определение int x=0;.

Со всеми компиляторами (и, что немаловажно, компоновщиками) мне удалось попробовать, такого не бывает. Все платформы компиляции, которые я пробовал, связывают эти два файла, и значение x равно 2 в обоих файлах.

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

Есть мысли по этому поводу? Другие интерпретации стандарта? Названия платформ, на которых файлы f1.c и f2.c отказываются связываться между собой?

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


  • Спасибо, что поделился. никогда не бывает слишком стар, чтобы учиться 29.09.2009
  • Компилятору нужно отклонять (то есть предупреждать или выдавать ошибку) вещи только тогда, когда вы нарушаете вещи в абзаце ограничения. Ограничение, заключающееся в том, что у вас может не быть двух внешних определений для ваших вещей, должно быть вне абзаца ограничения. Нарушение любого shall за пределами ограничения автоматически приводит к неопределенному поведению в C - это то, что позволяет компилятору обрабатывать его так, как он хочет. 29.09.2009
  • @litb Это интересный момент. Упомянутый мною статический анализатор старается, когда это возможно, не отмечать /установленные/ методы программирования, даже если они не определены стандартом. Здесь, я думаю, мы решим не предупреждать, так как на платформе, на которой эти множественные определения не поддерживаются, вероятно они приведут к сбою во время компоновки, а не во время выполнения. PS: я знаю, что означает undefined, но каждая дополнительная опция анализа делает анализатор немного менее пригодным для использования, и это должно быть взвешено по сравнению с прибылью. Отсюда Названия платформ, на которых... часть вопроса 29.09.2009
  • В последних версиях gcc по умолчанию используется -fno-common. Тогда вы получите ошибку компоновщика, даже если у вас просто int x; без инициализации в f2.c. Слияние предварительных определений между единицами компиляции - это плохо, ИМХО. Это приведет к ошибкам. Ключевое слово extern теперь существует, чтобы делать все правильно. 23.01.2021

Ответы:


1

См. также Что такое внешние переменные в C. Это упоминается в стандарте C в информационном приложении J как общее расширение:

J.5.11 Несколько внешних определений

Может быть более одного внешнего определения идентификатора объекта с явным использованием ключевого слова extern или без него; если определения не совпадают или инициализировано более одного, поведение не определено (6.9.2).

Предупреждение

Как указывает здесь @litb и как указано в моем ответе на вопрос с перекрестными ссылками, использование нескольких определений для глобальной переменной приводит к неопределенному поведению, что является стандартным способом сказать, что все может случиться. Одна из вещей, которая может случиться, состоит в том, что программа ведет себя так, как вы ожидаете; и J.5.11 говорит, что приблизительно вам может везти чаще, чем вы того заслуживаете. Но программа, которая опирается на несколько определений переменной extern — с явным ключевым словом extern или без него — не является строго соответствующей программой и не гарантирует, что будет работать везде. Эквивалентно: он содержит ошибку, которая может проявляться, а может и не проявляться.

29.09.2009
  • Хотя я специально спрашивал о не внешних переменных, в этом абзаце действительно есть интересное уточнение. Спасибо за ссылку. Вот что мне нравится в стандартах... 29.09.2009
  • Поскольку обе переменные находятся в области действия файла и не являются статическими (они должны быть такими, чтобы вообще возникала какая-либо проблема), они обе являются «внешними» — с явным использованием ключевого слова extern или без него. 29.09.2009
  • Создает ли каждая компиляция единицы перевода с предварительным определением место в памяти для него, в то время как компоновщик просто сохраняет его, или как это сработает? С другой стороны, использование ключевого слова extern не должно создавать никаких ячеек памяти, не так ли? 29.09.2009
  • @Jonathan Я имел в виду, что специально спрашивал о переменных без ключевого слова «extern». @olovb Я так это вижу. Команда Unix 'nm' (применяемая к объектным файлам) также производит такое впечатление. 29.09.2009
  • так что это подчеркивает важность соглашений об именах и/или пространств имен. Глобальная переменная «x» отлично подходит для образовательных целей, но вряд ли уникальна в реальной жизни.... 29.09.2009
  • Чтобы было действительно ясно, разрешено это или нет: нет, это неопределенное поведение в C. Это похоже на выполнение a[10] = 0;, даже если a является int a[1];, что также было и разрешено как общее расширение (до того, как у нас появились гибкие элементы массива). Я думаю, следует четко отметить, что это формально неопределенное поведение, в дополнение к определенному поведению на некоторых платформах. 29.09.2009
  • @Jonathan, извините, если я немного раздражаю своими комментариями UB :) Я просто подумал, что спрашивающий может подумать, услышав общее расширение, что стандарт C каким-то образом позволяет программам делать это и оставаться в строгом соответствии :) +1 ' ты конечно 29.09.2009
  • @litb: NP - это, как вы говорите, формально не разрешено стандартом, и программы, которые определяют одно и то же имя глобальной переменной несколько раз, не являются строго соответствующими или максимально переносимыми. И вопрос/ответ с перекрестными ссылками также проходит через это. Я также прямо укажу на это в этом ответе; это важный момент. 29.09.2009
  • Обратите внимание, что ключевое слово extern делает его объявлением, а не определением, поэтому невозможно иметь несколько определений с явным ключевым словом extern. 01.06.2012
  • отличный ответ, мистер @JonathanLeffler 20.12.2013
  • @JohannesSchaub-litb: +1 Я думал, что спрашивающий может подумать, услышав распространенное расширение, что стандарт C каким-то образом позволяет программам делать это и оставаться строго соответствующими - Это должно устранить неоднозначность того, что означает общее расширение, и развеять ваши опасения. 03.03.2014
  • @ legends2k: Во многих отношениях «общее расширение» в этом контексте имеет двусмысленный смысл. Приложение J.5 стандарта 2011 года называется «Общие расширения». В списке общих расширений раздел J.5.11 «Несколько внешних определений» напоминает об «общем» механизме Фортрана. Таким образом, его можно рассматривать как «ОБЩЕЕ общее расширение». 03.03.2014
  • @JonathanLeffler: понял двусмысленность. Я не знал, что в стандарте на самом деле перечислены расширения, обычно поддерживаемые компиляторами языка; правильно ли я понимаю, что программа, использующая одно такое расширение, все еще не является строгим C11? 04.03.2014
  • @ legends2k: да, использование одного из распространенных расширений делает программу не строго соответствующей C11, но означает (примерно), что вам может сойти с рук это чаще, чем можно было бы ожидать, прочитав остальную часть стандарта. 04.03.2014

  • 2

    Существует так называемое «общее расширение» стандарта, в котором допускается многократное определение переменных, если переменная инициализируется только один раз. См. https://c-faq.com/decl/decldef.html.

    На связанной странице говорится, что это относится к платформам Unix - я думаю, это то же самое для c99, что и для c89 - хотя, возможно, это было принято большим количеством компиляторов для формирования своего рода стандарта де-факто. Интересный.

    29.09.2009

    3

    Это должно прояснить мой ответ на комментарий olovb:

    вывод nm для объектного файла, скомпилированного из "int x;". На этой платформе символы начинаются с «_», то есть переменная x отображается как _x.

    00000000 T _main
             U _unknown
    00000004 C _x
             U dyld_stub_binding_helper
    

    вывод nm для объектного файла, скомпилированного из "int x=1;"

    00000000 T _main
             U _unknown
    000000a0 D _x
             U dyld_stub_binding_helper
    

    вывод nm для объектного файла, скомпилированного из "int x=0;"

    00000000 T _main
             U _unknown
    000000a0 D _x
             U dyld_stub_binding_helper
    

    вывод nm для объектного файла, скомпилированного из "extern int x;"

    00000000 T _main
             U _unknown
             U dyld_stub_binding_helper
    

    РЕДАКТИРОВАТЬ: вывод nm для объектного файла, скомпилированного из «extern int x;» где x фактически используется в одной из функций

    00000000 T _main
             U _unknown
             U _x
             U dyld_stub_binding_helper
    
    29.09.2009
  • На случай, если кто-то не знаком с выводом nm: определяется D. U не определено. а от man nm - C Символ общий. Общие символы представляют собой неинициализированные данные. При связывании может появиться несколько общих символов с одним и тем же именем. Если символ определен где-либо, общие символы рассматриваются как неопределенные ссылки. 29.09.2009
  • Спасибо за пояснение, Паскаль. 01.10.2009
  • Новые материалы

    Как проанализировать работу вашего классификатора?
    Не всегда просто знать, какие показатели использовать С развитием глубокого обучения все больше и больше людей учатся обучать свой первый классификатор. Но как только вы закончите..

    Работа с цепями Маркова, часть 4 (Машинное обучение)
    Нелинейные цепи Маркова с агрегатором и их приложения (arXiv) Автор : Бар Лайт Аннотация: Изучаются свойства подкласса случайных процессов, называемых дискретными нелинейными цепями Маркова..

    Crazy Laravel Livewire упростил мне создание электронной коммерции (панель администратора и API) [Часть 3]
    Как вы сегодня, ребята? В этой части мы создадим CRUD для данных о продукте. Думаю, в этой части я не буду слишком много делиться теорией, но чаще буду делиться своим кодом. Потому что..

    Использование машинного обучения и Python для классификации 1000 сезонов новичков MLB Hitter
    Чему может научиться машина, глядя на сезоны новичков 1000 игроков MLB? Это то, что исследует это приложение. В этом процессе мы будем использовать неконтролируемое обучение, чтобы..

    Учебные заметки: создание моего первого пакета Node.js
    Это мои обучающие заметки, когда я научился создавать свой самый первый пакет Node.js, распространяемый через npm. Оглавление Глоссарий I. Новый пакет 1.1 советы по инициализации..

    Забудьте о Matplotlib: улучшите визуализацию данных с помощью умопомрачительных функций Seaborn!
    Примечание. Эта запись в блоге предполагает базовое знакомство с Python и концепциями анализа данных. Привет, энтузиасты данных! Добро пожаловать в мой блог, где я расскажу о невероятных..

    ИИ в аэрокосмической отрасли
    Каждый полет – это шаг вперед к великой мечте. Чтобы это происходило в их собственном темпе, необходима команда астронавтов для погони за космосом и команда технического обслуживания..


    Для любых предложений по сайту: [email protected]