Влияние ключевого слова extern на функции C - PullRequest
159 голосов
/ 13 мая 2009

В C я не заметил никакого эффекта от ключевого слова extern, использованного до объявления функции. Сначала я подумал, что при определении extern int f(); в одном файле заставляет реализовать его вне области действия файла. Однако я узнал, что оба:

extern int f();
int f() {return 0;}

и

extern int f() {return 0;}

компилируется просто отлично, без предупреждений от gcc. Я использовал gcc -Wall -ansi; он даже не принял бы // комментариев.

Существуют ли какие-либо эффекты для использования extern перед определениями функций ? Или это просто необязательное ключевое слово без побочных эффектов для функций.

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

РЕДАКТИРОВАТЬ: Чтобы уточнить, я знаю, что есть использование для extern в переменных, но я спрашиваю только о extern в функциях .

Ответы [ 9 ]

126 голосов
/ 13 мая 2009

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

Это очень полезно, если вам нужно разделить какой-то глобал между модулями и не хотите помещать / инициализировать его в заголовке.

Технически, каждая функция в общедоступном заголовке библиотеки является 'extern', однако, в зависимости от компилятора, маркировка их как таковых имеет очень мало или вообще никаких преимуществ. Большинство компиляторов могут понять это самостоятельно. Как видите, эти функции на самом деле определены где-то еще.

В приведенном выше примере main () напечатает hello world только один раз, но продолжит ввод bar_function (). Также обратите внимание, что bar_function () не собирается возвращаться в этом примере (так как это всего лишь простой пример). Просто представьте, что stop_now модифицируется, когда сигнал обслуживается (следовательно, изменчив), если это не кажется достаточно практичным.

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

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

76 голосов
/ 13 мая 2009

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

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

20 голосов
/ 13 мая 2009

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

Позвольте мне объяснить:

Допустим, файл 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;
}

Полезен extern в заголовке, поскольку он сообщает компилятору на этапе компоновки: «это объявление, а не определение». Если я удаляю строку в a.c, которая определяет i, выделяет для нее место и присваивает ей значение, программа не сможет скомпилироваться с неопределенной ссылкой. Это говорит разработчику, что он ссылался на переменную, но еще не определил ее. Если, с другой стороны, я опускаю ключевое слово "extern" и удаляю строку int i = 2, программа все равно компилируется - для меня будет задано значение по умолчанию, равное 0.

Переменные области файла неявно определяются со значением по умолчанию 0 или NULL, если вы не присваиваете им значение явно - в отличие от переменных области блока, которые вы объявляете в верхней части функции. Ключевое слово extern избегает этого неявного определения и, следовательно, помогает избежать ошибок.

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

14 голосов
/ 13 мая 2009

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

7 голосов
/ 13 мая 2009

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

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

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

3 голосов
/ 13 мая 2009

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

2 голосов
/ 15 июля 2011

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

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

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

1 голос
/ 15 июня 2010

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

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

0 голосов
/ 18 мая 2019

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

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

См. коммит 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
...