Эффекты ключевого слова 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 в функциях.

159
13 мая '09 в 10:51
источник поделиться
9 ответов

У нас есть два файла, 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 модифицируется, когда сигнал обслуживается (следовательно, изменчив), если это не кажется достаточно практичным.

Экстерьеры очень полезны для таких вещей, как обработчики сигналов, мьютекс, который вы не хотите помещать в заголовок или структуру и т.д. Большинство компиляторов оптимизируют работу, чтобы гарантировать, что они не резервируют память для внешних объектов, так как они знают, что они Я буду резервировать его в модуле, где определен объект. Однако, опять же, нет смысла указывать его современными компиляторами при создании прототипов открытых функций.

Надеюсь, это поможет :)

127
13 мая '09 в 11:19
источник

Связанные вопросы


Похожие вопросы

Насколько я помню стандарт, все объявления функций считаются "extern" по умолчанию, поэтому нет необходимости явно указывать его.

Это не делает это ключевое слово бесполезным, так как оно также может использоваться с переменными (и в этом случае - это единственное решение для решения проблем с привязкой). Но с функциями - да, это необязательно.

75
13 мая '09 в 11:03
источник

Вам нужно различать две отдельные концепции: определение функции и объявление символа. "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 позволяет избежать этого неявного определения и, таким образом, помогает избежать ошибок.

Для функций в объявлениях функций ключевое слово действительно избыточно. Объявление функций не имеет неявного определения.

20
13 мая '09 в 19:33
источник

Ключевое слово 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. Если объявление идентификатора для объекта имеет область действия файла и спецификатор класса хранения, его связь внешняя.

14
13 мая '09 в 11:56
источник

В строковых функциях специальные правила о том, что означает extern. (Обратите внимание, что встроенные функции являются расширением C99 или GNU, они не были в оригинальном C.

Для не-встроенных функций extern не требуется, поскольку по умолчанию он включен.

Обратите внимание, что правила для С++ разные. Например, extern "C" требуется в объявлении С++ для функций C, которые вы собираетесь вызывать из С++, и существуют разные правила о inline.

7
13 мая '09 в 19:44
источник

Ключевое слово extern информирует компилятор о том, что функция или переменная имеет внешнюю привязку - другими словами, она видна из файлов, отличных от тех, в которых она определена. В этом смысле ключевое слово static имеет противоположный смысл. Немного странно поставить extern во время определения, поскольку никакие другие файлы не будут иметь видимость определения (или это приведет к нескольким определениям). Обычно вы помещаете extern в объявление в какой-то момент с внешней видимостью (например, файл заголовка) и помещаете определение в другое место.

3
13 мая '09 в 11:02
источник

Объявление функции extern означает, что ее определение будет разрешено во время связывания, а не во время компиляции.

В отличие от обычных функций, которые не объявлены extern, их можно определить в любом из исходных файлов (но не в нескольких исходных файлах, иначе вы получите сообщение об ошибке компоновщика с указанием нескольких определений функции), включая тот, в котором он объявлен extern.So, в ур случае линкер разрешает определение функции в том же файле.

Я не думаю, что это было бы очень полезно, но такие эксперименты дают лучшее представление о том, как работает компилятор языка и компоновщик.

2
15 июля '11 в 9:40
источник

Причина, по которой это не имеет никакого эффекта, заключается в том, что в момент ссылки компоновщик пытается разрешить определение extern (в вашем случае extern int f()). Не имеет значения, найдет ли он его в том же файле или в другом файле, если он найден.

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

1
15 июня '10 в 4:16
источник

IOW, extern избыточен и ничего не делает.

Вот почему 10 лет спустя:

  • Инструмент преобразования кода, такой как Coccinelle, будет стремиться extern в объявлении функции для удаления;
  • кодовая база, такая как git/git следует этому выводу и удаляет extern из своего кода (для Git 2.22, Q2 2019).

См. Коммит ad6dad0, коммит b199d71, коммит 5545442 (29 апреля 2019 г.) Дентона Лю (Denton-L).
(Объединено Junio C Hamano - gitster - в коммите 4aeeef3, 13 мая 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
0
18 мая '19 в 23:25
источник

Посмотрите другие вопросы по меткам или Задайте вопрос