Эффекты ключевого слова extern для функций C
В C я не заметил никакого эффекта ключевого слова extern
, используемого перед объявлением функции.
Во-первых, я думал, что при определении extern int f();
в одном файле принудительно вы реализуете его за пределами области файла. Однако я узнал, что оба:
extern int f();
int f() {return 0;}
и
extern int f() {return 0;}
скомпилировать просто отлично, без предупреждений от gcc. Я использовал gcc -Wall -ansi
; он даже не примет комментарии //
.
Есть ли эффекты для использования extern
перед определениями функций? Или это просто необязательное ключевое слово без побочных эффектов для функций.
В последнем случае я не понимаю, почему стандартные дизайнеры решили заманить грамматику лишними ключевыми словами.
EDIT: Чтобы уточнить, я знаю, что использование для extern
в переменных, но я только спрашиваю о extern
в функциях.
Ответы
Ответ 1
У нас есть два файла, 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 модифицируется, когда сигнал обслуживается (следовательно, изменчив), если это не кажется достаточно практичным.
Экстерьеры очень полезны для таких вещей, как обработчики сигналов, мьютекс, который вы не хотите помещать в заголовок или структуру и т.д. Большинство компиляторов оптимизируют работу, чтобы гарантировать, что они не резервируют память для внешних объектов, так как они знают, что они Я буду резервировать его в модуле, где определен объект. Однако, опять же, нет смысла указывать его современными компиляторами при создании прототипов открытых функций.
Надеюсь, это поможет :)
Ответ 2
Насколько я помню стандарт, все объявления функций считаются "extern" по умолчанию, поэтому нет необходимости явно указывать его.
Это не делает это ключевое слово бесполезным, так как оно также может использоваться с переменными (и в этом случае - это единственное решение для решения проблем с привязкой). Но с функциями - да, это необязательно.
Ответ 3
Вам нужно различать две отдельные концепции: определение функции и объявление символа. "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
полезно, если вы явно хотите избежать определения переменной.
Позвольте мне объяснить:
Скажем, файл a.c содержит:
#include "a.h"
int i = 2;
int f() { i++; return i;}
В файл a.h входят:
extern int i;
int f(void);
а файл b.c содержит:
#include <stdio.h>
#include "a.h"
int main(void){
printf("%d\n", f());
return 0;
}
Внешний текст в заголовке полезен, поскольку он сообщает компилятору во время фазы ссылки: "это объявление, а не определение". Если я удалю строку в a.c, которая определяет i, выделяет для нее место и присваивает ему значение, программа не должна компилироваться с помощью ссылки undefined. Это говорит разработчику, что он ссылался на переменную, но еще не определил ее. Если, с другой стороны, я опускаю ключевое слово "extern" и удаляю строку int i = 2
, программа все еще компилирует - я будет определена со значением по умолчанию 0.
Переменные области видимости файла неявно определяются со значением по умолчанию 0 или NULL, если вы явно не назначаете для них значение - в отличие от переменных области кадра, которые вы объявляете в верхней части функции. Ключевое слово extern позволяет избежать этого неявного определения и, таким образом, помогает избежать ошибок.
Для функций в объявлениях функций ключевое слово действительно избыточно. Объявление функций не имеет неявного определения.
Ответ 4
Ключевое слово 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. Если объявление идентификатора для объекта имеет область действия файла и спецификатор класса хранения, его связь внешняя.
Ответ 5
В строковых функциях специальные правила о том, что означает extern
. (Обратите внимание, что встроенные функции являются расширением C99 или GNU, они не были в оригинальном C.
Для не-встроенных функций extern
не требуется, поскольку по умолчанию он включен.
Обратите внимание, что правила для С++ разные. Например, extern "C"
требуется в объявлении С++ для функций C, которые вы собираетесь вызывать из С++, и существуют разные правила о inline
.
Ответ 6
Ключевое слово extern
информирует компилятор о том, что функция или переменная имеет внешнюю привязку - другими словами, она видна из файлов, отличных от тех, в которых она определена. В этом смысле ключевое слово static
имеет противоположный смысл. Немного странно поставить extern
во время определения, поскольку никакие другие файлы не будут иметь видимость определения (или это приведет к нескольким определениям). Обычно вы помещаете extern
в объявление в какой-то момент с внешней видимостью (например, файл заголовка) и помещаете определение в другое место.
Ответ 7
IOW, extern is redundant, и does nothing.
Вот почему 10 лет спустя:
Смотрите коммит ad6dad0, коммит b199d71, коммит 5545442 (29 апреля 2019 г.) от Дентон Лю (Denton-L
).
.
spatch
Была попытка удалить extern
из объявлений функций.
Удалите несколько экземпляров "extern
" для объявлений функций, которые перехватываются 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
Это не всегда просто:
См. коммит 7027f50 (04 сентября 2019 г.) от Дентона Лю (Denton-L
)
.
compat/*.[ch]
: удалить extern
из объявлений функций, используя spatch
В 5545442 (*.[ch]
: удалить extern
из объявлений функций, используя spatch, 2019-04-29, Git v2.22.0-rc0), мы удалили внешние элементы из объявлений функций, используя spatch
, но мы намеренно исключили файлы из compat/
, поскольку некоторые из них непосредственно копируются из восходящего потока, и мы должны избегать их чередования что ручное объединение будущих обновлений будет проще.
В последнем коммите мы определили файлы, которые были взяты из апстрима поэтому мы можем исключить их и запустить spatch
для оставшейся части.
Это был патч Coccinelle:
@@
type T;
identifier f;
@@
- extern
T f(...);
и он был запущен с помощью:
$ git ls-files compat/\*\*.{c,h} |
xargs spatch --sp-file contrib/coccinelle/noextern.cocci --in-place
$ git checkout -- \
compat/regex/ \
compat/inet_ntop.c \
compat/inet_pton.c \
compat/nedmalloc/ \
compat/obstack.{c,h} \
compat/poll/
У Coccinelle есть некоторые проблемы с __attribute__
и Варажами, поэтому мы запустили следующее, чтобы убедиться, что не осталось никаких изменений за:
$ git ls-files compat/\*\*.{c,h} |
xargs sed -i'' -e 's/^\(\s*\)extern \([^(]*([^*]\)/\1\2/'
$ git checkout -- \
compat/regex/ \
compat/inet_ntop.c \
compat/inet_pton.c \
compat/nedmalloc/ \
compat/obstack.{c,h} \
compat/poll/
Обратите внимание, что в Git 2.24 (Q4 2019) любое поддельное extern
отбрасывается.
См. коммит 65904b8 (30 сентября 2019 г.) Эмили Шаффер (nasamuffin
).
Помощник: Джефф Кинг (peff
).
См. коммит 8464f94 (21 сентября 2019 г.) от Дентона Лю (Denton-L
).
Помощник: Джефф Кинг (peff
).
(Merged by Junio C Hamano -- [TG432] -- in commit 59b19bc, 07 Oct 2019)
promisor-remote.h
: убрать extern
из объявления функции
Во время создания этого файла каждый раз, когда вводилось новое объявление функции, он включал extern
.
Однако, начиная с 5545442 (*.[ch]
: удалите extern
из объявлений функций, используя spatch
, 2019-04-29, Git v2.22.0-rc0), мы активно пытались предотвратить используется в объявлениях функций, потому что они не нужны.
Удалите эти ложные сообщения extern
.
Ответ 8
Объявление функции extern означает, что ее определение будет разрешено во время связывания, а не во время компиляции.
В отличие от обычных функций, которые не объявлены extern, их можно определить в любом из исходных файлов (но не в нескольких исходных файлах, иначе вы получите сообщение об ошибке компоновщика с указанием нескольких определений функции), включая тот, в котором он объявлен extern.So, в ур случае линкер разрешает определение функции в том же файле.
Я не думаю, что это было бы очень полезно, но такие эксперименты дают лучшее представление о том, как работает компилятор языка и компоновщик.
Ответ 9
Причина, по которой это не имеет никакого эффекта, заключается в том, что в момент ссылки компоновщик пытается разрешить определение extern (в вашем случае extern int f()
). Не имеет значения, найдет ли он его в том же файле или в другом файле, если он найден.
Надеюсь, это ответит на ваш вопрос.