Влияние ключевого слова extern на функции C
В Си я не заметил никакого эффекта от extern
Ключевое слово, используемое до объявления функции. Сначала я думал, что при определении extern int f();
в одном файле вынуждает вас реализовать его вне области видимости файла. Однако я узнал, что оба:
extern int f();
int f() {return 0;}
а также
extern int f() {return 0;}
компилируется просто отлично, без предупреждений от gcc. я использовал gcc -Wall -ansi
; это даже не примет //
Комментарии.
Есть ли какие-либо эффекты для использования extern
перед определениями функций? Или это просто необязательное ключевое слово без побочных эффектов для функций.
В последнем случае я не понимаю, почему стандартные дизайнеры решили засорять грамматику лишними ключевыми словами.
РЕДАКТИРОВАТЬ: Чтобы уточнить, я знаю, что есть использование для extern
в переменных, но я спрашиваю только о extern
в функции.
10 ответов
У нас есть два файла, foo.c и bar.c.
Вот foo.c
#include <stdio.h>
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 <stdio.h>
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, вы сообщаете компилятору, что все, что следует за ним, будет найдено (нестатично) во время ссылки; не оставляйте ничего для этого в текущем проходе, так как это будет встречено позже. Функции и переменные обрабатываются одинаково в этом отношении.
Это очень полезно, если вам нужно разделить некоторые глобальные значения между модулями и не хотите помещать / инициализировать их в заголовке.
Технически, каждая функция в общедоступном заголовке библиотеки является "внешней", однако маркировка их как таковых имеет очень мало или вообще никаких преимуществ, в зависимости от компилятора. Большинство компиляторов могут понять это самостоятельно. Как видите, эти функции на самом деле определены где-то еще.
В приведенном выше примере main() напечатает hello world только один раз, но продолжит ввод bar_function(). Также обратите внимание, что bar_function () не собирается возвращаться в этом примере (так как это простой пример). Просто представьте, что stop_now модифицируется, когда сигнал обслуживается (следовательно, изменчив), если это не кажется достаточно практичным.
Экстерьеры очень полезны для таких вещей, как обработчики сигналов, мьютекс, который вы не хотите помещать в заголовок или структуру, и т. Д. Большинство компиляторов оптимизируют работу, чтобы гарантировать, что они не резервируют память для внешних объектов, так как они знают, что они Я буду резервировать его в модуле, где определен объект. Однако, опять же, нет смысла указывать его современными компиляторами при создании прототипов открытых функций.
Надеюсь, это поможет:)
Насколько я помню стандарт, все объявления функций по умолчанию считаются "внешними", поэтому нет необходимости указывать его явно.
Это не делает это ключевое слово бесполезным, поскольку его также можно использовать с переменными (и в этом случае - это единственное решение для решения проблем с сцеплением). Но с функциями - да, это необязательно.
Необходимо различать два отдельных понятия: определение функции и объявление символа. "extern" - это модификатор связи, подсказка компилятору о том, где определяется символ, на который ссылаются впоследствии (подсказка "не здесь").
Если я напишу
extern int i;
в области видимости файла (вне функционального блока) в файле C вы говорите: "переменная может быть определена в другом месте".
extern int f() {return 0;}
является одновременно объявлением функции f и определением функции f. Определение в этом случае переопределяет внешний.
extern int f();
int f() {return 0;}
сначала декларация, затем определение.
Использование extern
неверно, если вы хотите объявить и одновременно определить переменную области файла. Например,
extern int i = 4;
выдаст ошибку или предупреждение, в зависимости от компилятора.
Использование extern
полезно, если вы явно хотите избежать определения переменной.
Позволь мне объяснить:
Допустим, файл ac содержит:
#include "a.h"
int i = 2;
int f() { i++; return i;}
Файл ах включает в себя:
extern int i;
int f(void);
и файл bc содержит:
#include <stdio.h>
#include "a.h"
int main(void){
printf("%d\n", f());
return 0;
}
Экстерьер в заголовке полезен, потому что он сообщает компилятору на этапе компоновки: "это объявление, а не определение". Если я удалю строку в ac, которая определяет i, выделит для нее место и присвоит ей значение, программа не сможет скомпилироваться с неопределенной ссылкой. Это говорит разработчику, что он ссылался на переменную, но еще не определил ее. Если, с другой стороны, я опускаю ключевое слово "extern" и удаляю int i = 2
строка, программа все еще компилируется - я буду определен со значением по умолчанию 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 Для идентификатора, объявленного с помощью спецификатора класса хранения extern в области видимости, в котором видно предыдущее объявление этого идентификатора,23) если в предыдущем объявлении указана внутренняя или внешняя связь, связь идентификатора в последующем объявлении будет такой же в качестве связи, указанной в предыдущей декларации. Если никакое предыдущее объявление не видно или если в предыдущем объявлении не указана связь, то идентификатор имеет внешнюю связь.
5 Если объявление идентификатора для функции не имеет спецификатора класса хранения, его связь определяется точно так, как если бы он был объявлен с помощью спецификатора класса хранения extern. Если объявление идентификатора для объекта имеет область файла и не имеет спецификатора класса хранения, его связь является внешней.
Встроенные функции имеют специальные правила о том, что extern
средства. (Обратите внимание, что встроенные функции являются расширением C99 или GNU; их не было в оригинальном C.
Для не встроенных функций extern
не требуется, так как он включен по умолчанию.
Обратите внимание, что правила для C++ разные. Например, extern "C"
требуется для объявления C++ функций C, которые вы собираетесь вызывать из C++, и существуют разные правила inline
,
IOW, extern избыточен и ничего не делает.
Вот почему 10 лет спустя:
- Инструмент преобразования кода, такой как Coccinelle, будет стремиться помечать
extern
в объявлении функции для удаления; - кодовая база как
git/git
следует этому выводу и удаляетextern
из его кода (для Git 2.22, Q2 2019).
См. Коммит ad6dad0, коммит b199d71, коммит 5545442 (29 апреля 2019 г.) от Denton Liu ( Denton-L
)
(Объединено Юнио С Хамано - gitster
- в комитете 4aeeef3, 13 мая 2019 года)
*.[ch]
: Удалитьextern
из объявлений функций, использующихspatch
Там был толчок, чтобы удалить
extern
из объявлений функций.Удалить несколько экземпляров
extern
msgstr "для объявлений функций, которые перехватываются Coccinelle.
Обратите внимание, что у Coccinelle есть некоторые трудности с обработкой функций__attribute__
или Varargs так некоторыеextern
декларации оставлены для рассмотрения в будущем патче.Это был патч Coccinelle:
@@ type T; identifier f; @@ - extern T f(...);
и он был запущен с:
$ git ls-files \*.{c,h} | grep -v ^compat/ | xargs spatch --sp-file contrib/coccinelle/noextern.cocci --in-place
extern
Ключевое слово информирует компилятор о том, что функция или переменная имеет внешнюю связь - другими словами, что она видна из файлов, отличных от того, в котором она определена. В этом смысле он имеет противоположное значение static
ключевое слово. Это немного странно extern
на момент определения, поскольку никакие другие файлы не будут иметь видимости определения (или это приведет к нескольким определениям). Обычно ставишь extern
в объявлении в какой-то момент с внешней видимостью (например, заголовочный файл) и поместите определение в другом месте.
Объявление функции extern означает, что ее определение будет разрешено во время компоновки, а не во время компиляции.
В отличие от обычных функций, которые не объявлены как extern, они могут быть определены в любом из исходных файлов (но не в нескольких исходных файлах, в противном случае вы получите сообщение об ошибке компоновщика, говорящее о том, что вы дали несколько определений функции), включая одно в который объявлен extern.So, в этом случае компоновщик разрешает определение функции в том же файле.
Я не думаю, что делать это было бы очень полезно, однако проведение подобных экспериментов дает лучшее представление о том, как работает компилятор и компоновщик языка.
В C функции неявно определяются как, независимо от того, действительно ли указано ключевое слово.
Итак, код:
int f() {return 0;}
Компилятор будет рассматривать как
extern int f() {return 0;}
По сути, нет семантической разницы между определением типичной функции и определением, которому предшествует
extern
ключевое слово, как в этом примере. Вы можете прочитать более подробное объяснение этого на https://www.geeksforgeeks.org/understanding-extern-keyword-in-c/
Причина этого не в том, что во время компоновки компоновщик пытается разрешить внешнее определение (в вашем случае extern int f()
). Неважно, если он найдет его в том же файле или в другом файле, если он найден.
Надеюсь, что это ответ на ваш вопрос.