Как правильно использовать ключевое слово extern в C

190

Мой вопрос в том, когда ссылка на функцию должна быть указана с ключевым словом extern в C.

Я не вижу, когда это следует использовать на практике. Когда я пишу программу, все функции, которые я использую, становятся доступными через файлы заголовков, которые я включил. Итак, почему было бы полезно extern получить доступ к тому, что не было показано в файле заголовка?

Я мог подумать о том, как extern работает некорректно, и если да, пожалуйста, исправьте меня.

Изменить: Если вы extern что-то, когда оно является объявлением по умолчанию без ключевого слова в файле заголовка?

Теги:

11 ответов

243
Лучший ответ

"extern" изменяет привязку. С ключевым словом предполагается, что функция /variable доступна где-то в другом месте, и разрешение отложено на компоновщик.

Существует разница между "extern" на функциях и на переменных: по переменным он не создает экземпляр самой переменной, то есть не выделяет никакой памяти. Это нужно сделать где-то еще. Поэтому важно, если вы хотите импортировать переменную из другого места. Для функций это говорит только компилятору, что ссылка является extern. Поскольку это значение по умолчанию (вы используете ключевое слово "static", чтобы указать, что функция не связана с использованием extern linkage), вам не нужно явно ее использовать.

  • 1
    тогда почему то же самое есть в Git: очень популярное и современное программное обеспечение, проверьте это: github.com/git/git/blob/master/strbuf.h
  • 0
    K & R не замечает, что по умолчанию функция объявляется как "extern", однако этот ответ решает мою путаницу!
Показать ещё 2 комментария
164

extern сообщает компилятору, что эти данные определены где-то и будут связаны с компоновщиком.

С помощью ответов здесь и беседы с несколькими друзьями здесь приведен практический пример использования extern.

Пример 1 -, чтобы показать ловушку:

File stdio.h:

int errno;
/* other stuff...*/

myCFile1.c:
#include <stdio.h>

Code...

myCFile2.c:
#include <stdio.h>

Code...

Если myCFile1.o и myCFile2.o связаны, каждый из файлов c имеет отдельные копии errno. Это проблема, поскольку предполагается, что один и тот же errno доступен во всех связанных файлах.

Пример 2 - Исправление.

File stdio.h:

extern int errno;
/* other stuff...*/

File stdio.c

int errno;

myCFile1.c:
#include <stdio.h>

Code...

myCFile2.c:
#include <stdio.h>

Code...

Теперь, если оба myCFile1.o и MyCFile2.o связаны линкером, они оба указывают на тот же errno. Таким образом, решая реализацию с помощью extern.

  • 64
    Проблема не в том, что модули myCFile1 и myCFile2 имеют отдельную копию errno, а в том, что они оба выставляют символ, называемый «errno». Когда компоновщик видит это, он не знает, какое «errno» выбрать, поэтому он выдает сообщение об ошибке.
  • 2
    что на самом деле означает «связанный линкером»? все используют этот термин, я не нахожу никакого определения :(
Показать ещё 8 комментариев
27

Уже указано, что ключевое слово extern избыточно для функций.

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

  • 0
    @aib "Избыточный для функций", проверьте мой комментарий в ответе Bluebrother.
  • 0
    Что если вы не хотите показывать какие-либо функции в заголовочном файле? Не лучше ли объявить переменную в одном C-файле и получить к ней доступ через extern в другом; пусть компоновщик решит проблему и скроет остальную часть заголовка.
14

В C термин "extern" подразумевается для прототипов функций, поскольку прототип объявляет функцию, которая определена где-то в другом месте. Другими словами, прототип функции имеет внешнюю связь по умолчанию; использование "extern" в порядке, но избыточно.

(Если требуется статическая привязка, функция должна быть объявлена ​​как "статическая" как в своем прототипе, так и в заголовке функции, и они обычно должны быть в одном файле .c).

10

Много лет спустя я открываю этот вопрос. Прочитав все ответы и комментарии, я мог бы прояснить несколько деталей... Это может быть полезно для людей, которые попадают сюда через поиск очков.

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

Позвольте определить 3 прототипа функций

//--------------------------------------
//Filename: "my_project.H"
extern int function_1(void);
static int function_2(void);
       int function_3(void);

Файл заголовка может использоваться основным исходным кодом, как показано ниже.

//--------------------------------------
//Filename: "my_project.C"
#include "my_project.H"

void main(void){
    int v1 = function_1();
    int v2 = function_2();
    int v3 = function_3();
}

int function_2(void) return 1234;

Чтобы скомпилировать и связать ссылку, мы должны определить "function_2" в том же файле исходного кода, где мы вызываем эту функцию. Две другие функции могут быть определены в другом исходном коде ".C" или они могут быть расположены в любом двоичном файле (.OBJ, *.LIB, *.DLL), для которого у нас может не быть исходного кода.

Давайте снова включим заголовок "my_project.H" в другой файл "*.C", чтобы лучше понять разницу. В том же проекте мы добавим следующий файл // --------------------------------------

//Filename: "my_big_project_splitted.C"
#include "my_project.H"

void old_main_test(void){
    int v1 = function_1();
    int v2 = function_2();
    int v3 = function_3();
}

int function_2(void) return 5678;

int function_1(void) return 12;
int function_3(void) return 34;

Важные функции для уведомления: Когда функция определена как "статическая" в файле заголовка, компилятор/компоновщик должен найти экземпляр функции с этим именем в каждом модуле, который использует этот файл include.

Функция, которая является частью библиотеки C, может быть заменена только одним модулем, переопределяя прототип с "статическим" только в этом модуле. Например, замените любой вызов "malloc" и "free", чтобы добавить функцию обнаружения утечки памяти.

Спецификатор "extern" на самом деле не нужен для функций. Когда "статический" не найден, функция всегда считается "extern" .

Однако "extern" не является значением по умолчанию для переменных. Обычно любой заголовочный файл, определяющий переменные, видимые во многих модулях, должен использовать "extern" . Единственным исключением было бы, если заголовочный файл гарантированно будет включен из одного и только одного модуля.

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

  • 0
    Так почему же статическая функция нуждается в определении по сравнению с внешними? (Я знаю, что это на 2 года позже, но это действительно очень полезно для понимания)
  • 2
    Определение необходимо, если вы вызываете функцию в строке 100 и создаете ее экземпляр в строке 500. В строке 100 будет объявлен неопределенный прототип. Итак, вы добавляете прототип вверху.
8

Очень хорошая статья о том, что я пришел с ключевым словом extern, а также с примерами: http://www.geeksforgeeks.org/understanding-extern-keyword-in-c/

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

  • 2
    Я прочитал статью на geeksforgeeks.org до того, как пришел сюда, но нашел ее довольно плохо написанной. Помимо грамматических и синтаксических недостатков, он использует много слов, чтобы подчеркнуть одно и то же несколько раз, а затем пропускает важную информацию. Например, в Примере 4 неожиданно включается «somefile.h», но об этом ничего не сказано, кроме: «Предположим, что somefile.h имеет определение var». Ну, информация, которую мы «предполагаем», просто та информация, которую я ищу. К сожалению, ни один из ответов на этой странице не намного лучше.
5

Если каждый файл в вашей программе сначала скомпилирован в объектный файл, тогда объектные файлы связаны между собой, вам нужно extern. Он сообщает компилятору "Эта функция существует, но код для нее находится где-то в другом месте. Не паникуйте".

  • 0
    Хм, вот как обычно выполняется перевод: исходные файлы компилируются в объектные файлы, а затем связываются. Когда вам не нужен extern в этом случае? Также вы не используете #include для получения функций, а скорее прототипы функций. Я не понимаю о чем ты говоришь.
  • 0
    Кажется, в последнее время у меня возникла проблема неправильного чтения. Извини за это. Когда я был новичком в C, я #include "file.c", чтобы просто включить функции из одного файла непосредственно в другой файл. Затем я понял, как использовать «extern». Я думал, что он совершил ту же ошибку, что и я.
4

Все декларации функций и переменных в файлах заголовков должны быть extern.

Исключения из этого правила являются встроенными функциями, определенными в заголовке, и переменными, которые, хотя и определены в заголовке, должны быть локальными для единицы перевода (исходный файл, в который заголовок включается): они должны быть static.

В исходных файлах extern не следует использовать для функций и переменных, определенных в файле. Просто префикс локальных определений с помощью static и ничего не делайте для общих определений - по умолчанию они будут внешними символами.

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


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

  • 0
    Можете ли вы объяснить, почему «Все объявления функций и переменных в заголовочных файлах должны быть внешними»? Из других ответов мне кажется, что они внешние по умолчанию.
  • 0
    @Lane: extern необязателен для объявлений функций, но мне нравится обрабатывать переменные и функции одинаково - по крайней мере, это наиболее разумная вещь, которую я могу придумать, поскольку я точно не помню, почему я начал это делать;)
Показать ещё 1 комментарий
2

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

В большинстве случаев ваши функции будут одно из следующих (более похоже на лучшую практику):

  • static (обычные функции, которые не являются видимый вне этого .c файла)
  • статический встроенный (inlines from.c или .h файлы)
  • extern (объявление в заголовках следующий вид (см. ниже))
  • [без ключевого слова вообще] (нормальный функции, предназначенные для доступа, используя объявления extern)
  • 0
    Зачем вам extern при объявлении прототипа, если это по умолчанию?
  • 0
    @Lane: Может быть немного предвзятым, но каждый здравомыслящий проект, над которым я работал, использует следующее соглашение: в заголовках объявляйте прототипы только для внешних функций (следовательно, extern). В файлах .c простые прототипы могут использоваться для устранения необходимости конкретного упорядочения, но их не следует размещать в заголовках.
2

Если у вас есть эта функция, определенная на другой dll или lib, чтобы компилятор отследил компоновщика, чтобы найти его. Типичный случай - когда вы вызываете функции из API OS.

0

Экстерн: "ключевое слово extern по умолчанию представляет перед любым объявлением переменной.Компилятор скрывает его.Когда вы хотите объявить функцию и она определена где-то в файле проекта, в таких условиях extern играет хорошую роль. Например, просто рассмотрите сокет программы, вы используете #include.Если вы пользователь Linux, найдите файл socket.h в своем компьютере и откройте его. Вы можете использовать множество объявлений функций extern. Это означает, что ваш заголовок" socket.h" объявлен всем требуемые функции, которые будут загружаться при запуске компиляции.Эти функции extern определены где-то в двоичных файлах сокетов.Еще нужно просто включить заголовочный файл. Ответственность линкера за загрузку двоичного файла для выполнения.

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню