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


236

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

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

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

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


Ответы:


290

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

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


1
тогда почему такая же внешняя вещь есть в Git: очень популярное и современное программное обеспечение, проверьте это: github.com/git/git/blob/master/strbuf.h
rsjethani

K & R не замечает, что по умолчанию функция объявляется как "extern", однако этот ответ решает мою путаницу!
Активист

@rsjethani Я думаю, что это должно сделать документ более строгим и форматным.
Активист

Может быть, глупый вопрос, но как это сравнить с предварительной декларацией?
weberc2

197

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 связаны компоновщиком, они оба будут указывать на одну и ту же ошибку . Таким образом, решаем реализацию с помощью extern .


71
Проблема не в том, что модули myCFile1 и myCFile2 имеют отдельную копию errno, а в том, что они оба выставляют символ, называемый «errno». Когда компоновщик видит это, он не знает, какое «errno» выбрать, поэтому он выдает сообщение об ошибке.
cvick

2
что на самом деле означает «связанный линкером»? все используют этот термин, я не нахожу никакого определения :(
Марсель Фальер

7
@MarcelFalliere Wiki ~ Компилятор компилирует каждый исходный файл самостоятельно и создает объектный файл для каждого исходного файла. Линкер связывает эти объектные файлы с 1 исполняемым файлом.
Bitterblue

1
@cwick gcc не выдает ошибку или предупреждение даже после использования -Wallи -pedantic. Зачем ? и как ?
б-ак

6
Разве охранник не защищает от этой вещи?
обскир

32

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

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


@aib "избыточно для функций", проверьте мой комментарий в ответе bluebrother.
rsjethani

Что если вы не хотите показывать какие-либо функции в заголовочном файле? Не лучше ли объявить переменную в одном C-файле и получить к ней доступ через extern в другом; пусть компоновщик решит проблему и скроет остальную часть заголовка.
ste3e

16

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

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

Давайте определим 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;

Важные особенности, на которые следует обратить внимание:

  • Когда функция определена как «статическая» в заголовочном файле, компилятор / компоновщик должен найти экземпляр функции с таким именем в каждом модуле, который использует этот включаемый файл.

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

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

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

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


Так почему же статическая функция нуждается в определении по сравнению с внешними? (Я знаю, что это на 2 года позже, но на самом деле это очень полезно для понимания)
SubLock69

2
Определение необходимо, если вы вызываете функцию в строке 100 и создаете ее экземпляр в строке 500. В строке 100 будет объявлен неопределенный прототип. Итак, вы добавляете прототип вверху.
Кристиан Гинграс,

15

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

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


8

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

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


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

6

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


Хм, вот как обычно выполняется перевод: исходные файлы компилируются в объектные файлы, а затем связываются. Когда вам не нужен extern в этом случае? Также вы не используете #include для получения функций, а скорее прототипы функций. Я не понимаю о чем ты говоришь.
Дэвид Торнли

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

4

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

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

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

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


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


Можете ли вы объяснить, почему «Все объявления функций и переменных в заголовочных файлах должны быть внешними»? Из других ответов мне кажется, что они являются внешними по умолчанию.
Lillq

@Lane: externявляется необязательным для объявлений функций, но мне нравится обрабатывать переменные и функции одинаково - по крайней мере, это наиболее разумная вещь, которую я могу придумать, поскольку я точно не помню, почему я начал это делать;)
Кристоф

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

3

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

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

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

Зачем вам extern при объявлении прототипа, если это по умолчанию?
Lillq

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

1

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.