Эффекты ключевого слова extern для функций C
В C я не заметил никакого эффекта ключевого слова extern
используемого перед объявлением функции. Сначала я думал, что при определении extern int f();
в одном файле заставляет вас реализовать его вне области действия. Однако я узнал, что оба:
extern int f(); int f() {return 0;}
а также
extern int f() {return 0;}
компилировать просто отлично, без предупреждений от gcc. Я использовал gcc -Wall -ansi
; он даже не примет //
комментарии.
- Где найти текущие стандартные документы C или C ++?
- Каковы новые возможности в C ++ 17?
- Подготовка к std :: iterator Будучи устаревшим
- Используйте CSS для автоматического добавления звездочки «обязательное поле» для формирования входных данных
- Почему компиляторы позволяют строковым литералам не быть const?
Есть ли эффекты для использования extern
перед определениями функций ? Или это просто необязательное ключевое слово без побочных эффектов для функций.
В последнем случае я не понимаю, почему стандартные дизайнеры решили заманить грамматику лишними ключевыми словами.
EDIT: Чтобы уточнить, я знаю, что есть использование для extern
в переменных, но я только спрашиваю о extern
в функциях .
- Что такое оператор «->» в C ++?
- Почему стандартные iteratorы диапазона вместо ?
- Почему стандарты C или C ++ явно не определяют символ как подписанный или неподписанный?
- Правильное изменение неопределенного поведения, если число больше ширины типа?
- Где именно стандарт C ++ говорит о разыменовании неинициализированного указателя - это неопределенное поведение?
- Это неуказанное поведение для сравнения указателей с разными массивами для равенства?
- Препроцессор C ++ идентичен препроцессору C?
- В чем разница между __PRETTY_FUNCTION__, __FUNCTION__, __func__?
У нас есть два файла: foo.c и bar.c.
Вот foo.c
#include volatile unsigned int stop_now = 0; extern void bar_function(void); int main(void) { while (1) { bar_function(); stop_now = 1; } return 0; }
Теперь, здесь bar.c
#include extern volatile unsigned int stop_now; void bar_function(void) { while (! stop_now) { printf("Hello, world!\n"); sleep(30); } }
Как вы можете видеть, у нас нет общего заголовка между foo.c и bar.c, однако для bar.c требуется что-то, объявленное в foo.c, когда оно связано, и foo.c нуждается в функции из bar.c, когда она связана.
Используя «extern», вы сообщаете компилятору, что все, что следует за ним, будет найдено (нестатическое) во время ссылки; не оставляйте за ним ничего в текущем проходе, так как он будет встречен позже. В этом отношении функции и переменные рассматриваются одинаково.
Это очень полезно, если вам нужно поделиться каким-то глобальным между модулями и не хотите ставить / инициализировать его в заголовке.
Технически каждая функция в публичном заголовке библиотеки является «extern», однако ее маркировка как таковая имеет очень мало пользы, в зависимости от компилятора. Большинство компиляторов могут понять это самостоятельно. Как вы видите, эти функции фактически определены где-то в другом месте.
В приведенном выше примере main () будет печатать мир hello только один раз, но продолжать вводить bar_function (). Также обратите внимание: bar_function () не будет возвращаться в этом примере (поскольку это просто простой пример). Представьте себе, что stop_now изменяется при обслуживании сигнала (следовательно, волатильности), если это не представляется практически достаточным.
Экстерны очень полезны для таких вещей, как обработчики сигналов, мьютексы, которые вы не хотите вводить в заголовок или структуру и т. Д. Большинство компиляторов оптимизируют, чтобы гарантировать, что они не резервируют память для внешних объектов, поскольку они знают, что они ‘Оставляем его в модуле, где объект определен. Однако, опять же, нет смысла указывать его с современными компиляторами при прототипировании публичных функций.
Надеюсь, это поможет 🙂
Насколько я помню стандарт, все объявления функций считаются «extern» по умолчанию, поэтому нет необходимости указывать его явно.
Это не делает это ключевое слово бесполезным, так как оно также может использоваться с переменными (и в этом случае – это единственное решение для решения проблем связи). Но с функциями – да, это необязательно.
Вам нужно различать две отдельные концепции: определение функции и объявление символа. «extern» – это модификатор привязки, подсказка для компилятора о том, где определен символ, обозначенный впоследствии (подсказка «не здесь»).
Если я напишу
extern int i;
в области файлов (вне функционального блока) в файле C, то вы говорите: «переменная может быть определена в другом месте».
extern int f() {return 0;}
является как объявлением функции f, так и определением функции f. Определение в этом случае превосходит extern.
extern int f(); int f() {return 0;}
это первое объявление, за которым следует определение.
Использование extern
неверно, если вы хотите объявить и одновременно определить переменную области файла. Например,
extern int i = 4;
выдаст ошибку или предупреждение в зависимости от компилятора.
Использование extern
полезно, если вы явно хотите избежать определения переменной.
Позволь мне объяснить:
Скажем, файл ac содержит:
#include "ah" int i = 2; int f() { i++; return i;}
Файл ah включает в себя:
extern int i; int f(void);
и файл bc содержит:
#include #include "ah" int main(void){ printf("%d\n", f()); return 0; }
Внешний текст в заголовке полезен, поскольку он сообщает компилятору во время фазы ссылки: «это объявление, а не определение». Если я удалю строку в переменную ac, которая определяет i, выделяет для нее место и присваивает ему значение, программа не должна компилироваться с неопределенной ссылкой. Это говорит разработчику, что он ссылался на переменную, но еще не определил ее. Если, с другой стороны, я опускаю ключевое слово «extern» и int i = 2
строку int i = 2
, программа все еще компилирует – i будет определено со значением по умолчанию 0.
Переменные области видимости неявно определяются с использованием значения по умолчанию 0 или NULL, если вы явно не назначаете им значение – в отличие от переменных области видимости, которые вы объявляете в верхней части функции. Ключевое слово extern позволяет избежать этого неявного определения и, таким образом, помогает избежать ошибок.
Для функций, в объявлениях функций, ключевое слово действительно избыточно. Объявление функций не имеет неявного определения.
Ключевое слово extern
принимает различные формы в зависимости от среды. Если объявление доступно, ключевое слово extern
принимает ссылку, указанную ранее в блоке перевода. В отсутствие такой декларации extern
указывает внешнюю связь.
static int g(); extern int g(); /* g has internal linkage */ extern int j(); /* j has tentative external linkage */ extern int h(); static int h(); /* error */
Вот соответствующие абзацы из проекта C99 (n1256):
6.2.2 Связи идентификаторов
[…]
4 Для идентификатора, объявленного с помощью спецификатора classа хранения extern в области видимости, в которой видна предварительная декларация этого идентификатора, 23), если предыдущее объявление указывает внутреннюю или внешнюю связь, связь идентификатора с последующим объявлением одинакова как связь, указанная в предыдущем объявлении. Если ни одно предварительное объявление не отображается, или если в предыдущем объявлении не указана ссылка, то идентификатор имеет внешнюю привязку.
5 Если декларация идентификатора для функции не имеет спецификатора classа хранения, ее привязка определяется точно так, как если бы она была объявлена с помощью спецификатора classа хранения extern. Если объявление идентификатора для объекта имеет область действия файла и спецификатор classа хранения, его связь является внешней.
Встроенные функции имеют специальные правила о том, что означает extern
. (Обратите внимание, что встроенные функции являются расширением C99 или GNU, а не оригиналом C.
Для не-встроенных функций extern
не требуется, поскольку он включен по умолчанию.
Обратите внимание, что правила для C ++ различны. Например, extern "C"
требуется в объявлении C ++ для функций C, которые вы собираетесь вызывать из C ++, и существуют разные правила о inline
.
Ключевое слово extern
сообщает компилятору, что функция или переменная имеет внешнюю связь – другими словами, она видна из файлов, отличных от тех, в которых она определена. В этом смысле ключевое слово static
имеет противоположное значение. Немного странно ставить extern
во время определения, поскольку никакие другие файлы не имели бы видимости определения (или это приводило бы к нескольким определениям). Обычно вы extern
в объявление в какой-то момент с внешней видимостью (например, файл заголовка) и ставите определение в другом месте.
объявление функции extern означает, что ее определение будет разрешено во время связывания, а не во время компиляции.
В отличие от обычных функций, которые не объявлены extern, их можно определить в любом из исходных файлов (но не в нескольких исходных файлах, иначе вы получите ошибку компоновщика, заявив, что вы дали несколько определений функции), включая который он объявлен extern.So, в ур случае линкер разрешает определение функции в том же файле.
Я не думаю, что делать это было бы очень полезно, но такие эксперименты дают лучшее представление о том, как работает компилятор и компоновщик языка.
Причина, по которой он не действует, заключается в том, что в момент ссылки компоновщик пытается разрешить определение extern (в вашем случае extern int f()
). Не имеет значения, находит ли он его в том же файле или в другом файле, если он найден.
Надеюсь, что это ответ на ваш вопрос.