Эффекты ключевого слова 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).
.

(Merged by Junio C Hamano -- [TG44] -- in commit 4aeeef3, 13 May 2019) *.[ch]: удалить extern из объявлений функций, используя

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)
.

(Merged by Denton Liu -- [TG415] -- in commit 7027f50, 05 Sep 2019)

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()). Не имеет значения, найдет ли он его в том же файле или в другом файле, если он найден.

Надеюсь, это ответит на ваш вопрос.