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

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

Другие вопросы по тегам