Зачем нам нужно ключевое слово '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, поскольку нет аналогичной концепции пробного определения.