Зачем нам нужно ключевое слово 'extern' в C, если объявления по умолчанию для файлов имеют внешнюю привязку?

AFAIK, любое объявление переменной или функции в области файлов имеет внешнюю привязку по умолчанию. static означает "он имеет внутреннюю связь", extern - "он может быть определен в другом месте", а не "имеет внешнюю связь".

Если да, зачем нам ключевое слово extern? Другими словами, в чем разница между int foo; и extern int foo; (область файла)?

Ответ 1

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

Ключевое слово позволяет компилятору отличить объявление объявления глобальной переменной от определения переменной:

extern double xyz; // Declares xyz without defining it

Если вы сохраните это выражение самостоятельно, а затем используйте xyz в своем коде, вы вызываете ошибку "undefined" во время фазы связывания.

double xyz; // Declares and defines xyz

Если вы сохраняете это объявление в файле заголовка и используете его из нескольких файлов C/С++, вы вызываете ошибку "множественных определений" во время фазы связывания.

Решение состоит в том, чтобы использовать extern в заголовке и не использовать extern в одном файле C или С++.

Ответ 2

В качестве иллюстрации, скомпилируйте следующую программу: (используя cc -c program.c или эквивалентную)

extern char bogus[0x12345678] ;

Теперь удалите ключевое слово "extern" и снова скомпилируйте:

char bogus[0x12345678] ="1";

Запустите objdump (или эквивалент) для двух объектов.

Вы обнаружите, что без фактического размещения ключевого слова extern.

  • С ключевым словом extern вся "фиктивная" вещь является только ссылкой. Вы говорите компилятору: "там должно быть char bogus[xxx], исправить его!"
  • Без ключевого слова extern вы говорите: "Мне нужно пространство для переменной char bogus[xxx], дайте мне это пространство!"

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

Ответ 3

Вы можете определить только одну переменную.

Если несколько файлов используют одну и ту же переменную, переменная должна быть явно объявлена ​​в каждом файле. Если вы делаете простой "int foo;" вы получите двойную ошибку определения. Используйте "extern", чтобы избежать дублирования ошибки определения. Extern - это как сказать компилятору "эй, эта переменная существует, но не создает ее, она определяется где-то еще".

Процесс сборки в C не является "умным". Он не будет искать все файлы, чтобы увидеть, существует ли переменная. Вы должны явно сказать, что переменная существует в текущем файле, но в то же время не создавайте ее дважды.

Даже в том же файле процесс сборки не очень умный. Он идет сверху вниз и не будет распознавать имя функции, если оно определено ниже точки использования, поэтому вы должны объявить его выше.

Ответ 4

Я собираюсь повторить то, что говорили другие, но цитируя и интерпретируя проект C99 N1256.

Сначала я подтверждаю ваше утверждение о том, что внешняя привязка является значением по умолчанию для области файлов 6.2.2/5 "Связи идентификаторов" :

Если объявление идентификатора для объекта имеет область действия файла и спецификатор класса хранения, его связь является внешней.

Точка путаницы заключается в том, что extern не только изменяет привязку, но и погодное объявление объекта является определением или нет. Это важно, потому что 6.9/5 "Внешние определения" говорит, что может быть только одно внешнее определение:

Внешнее определение - это внешнее объявление, которое также является определением функции (отличной от встроенного определения) или объекта. Если идентификатор, объявленный с внешней связью, используется в выражении (отличном от части операнда оператора sizeof, результат которого является целочисленной константой), где-то во всей программе должно быть ровно одно внешнее определение для идентификатора; в противном случае должно быть не более один.

где "внешнее определение" определяется фрагментом грамматики:

translation-unit:
  external-declaration

так что это означает объявление верхнего уровня файла.

Затем 6.9.2/2 "Внешние определения объектов" говорит (объект означает "данные переменной" ):

Объявление идентификатора для объекта с областью файлов без инициализатора и без спецификатора класса хранения или с помощью статического элемента класса хранения представляет собой предварительное определение. Если единица перевода содержит одно или несколько предварительных определений для идентификатора, а единица перевода не содержит внешнего определения для этого идентификатора, то поведение в точности совпадает с тем, что единица перевода содержит объявление области файла этого идентификатора, причем составной тип как конца блока перевода, причем инициализатор равен 0.

Итак:

extern int i;

не является определением, потому что у него есть спецификатор класса хранения: extern.

Однако:

int i;

не имеет спецификатора класса хранения, поэтому это предварительное определение. И если для i больше нет внешних объявлений, мы можем добавить инициализатор равным 0 = 0 неявно:

int i = 0;

Итак, если у нас было несколько int i; в разных файлах, компоновщик должен теоретически взорваться несколькими определениями.

GCC 4.8 не соответствует, и в качестве расширения допускается несколько int i; для разных файлов, как указано ниже: fooobar.com/info/441365/....

Это реализовано в ELF с общим символом, и это расширение настолько распространено, что оно упоминается в стандарте в J.5.11/5 Общие расширения > Несколько внешних определений:

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

Другое место, где extern имеет эффект в объявлениях области видимости блока, см.: Можно ли объявить локальные и регистровые переменные extern?

Если для объявления объекта есть инициализатор, extern не действует:

extern int i = 0;

равно

int i = 0;

Оба являются определениями.

Для функций extern, кажется, не имеет эффекта: Эффекты ключевого слова extern на функциях C, поскольку нет аналогичной концепции пробного определения.