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


235

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

Я не бачу, коли це потрібно використовувати на практиці. Коли я пишу програму, усі функції, які я використовую, доступні через файли заголовків, які я включив. То чому б було корисно externотримати доступ до того, що не було відкрито у файлі заголовка?

Я міг би подумати про те, як externпрацює неправильно, і якщо так, виправте мене.

Редагувати: чи потрібно вам externщось, коли це декларація за замовчуванням без ключового слова у файлі заголовка?


Відповіді:


290

" extern" змінює зв'язок. За ключовим словом функція / змінна вважається доступною десь ще, а роздільна здатність відкладається на лінкер.

Існує різниця між "extern" на функціях і на змінних: у змінних він не інстанціює саму змінну, тобто не виділяє жодної пам'яті. Це потрібно зробити ще десь. Тому важливо, якщо ви хочете імпортувати змінну з іншого місця. Для функцій це говорить лише компілятору, що зв'язок є зовнішньою. Оскільки це за замовчуванням (ви використовуєте ключове слово "статичний", щоб вказати, що функція не пов'язана за допомогою зовнішньої зв'язку), вам не потрібно використовувати її явно.


1
Тоді чому та сама штука у Git є: дуже популярне та сучасне програмне забезпечення перевіряйте це: github.com/git/git/blob/master/strbuf.h
rsjethani

K&R не зазначають, що функцію за замовчуванням оголошувати функцією "зовнішньою", однак ця відповідь вирішує мою плутанину!
акгент

@rsjethani Я думаю, що це зробити документ більш суворим та форматом.
акгент

Можливо, німе питання, але як це порівнюється з поданням декларації?
weberc2

196

екстерн повідомляє компілятору, що ці дані десь визначені і будуть пов'язані з лінкером.

За допомогою відповідей тут і розмовляти з кількома друзями тут є практичним прикладом використання екстерном .

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


70
Проблема не в тому, що модулі myCFile1 та myCFile2 мають окрему копію errno, це те, що вони обидва виявляють символ, який називається "errno". Коли лінкер бачить це, він не знає, яку "помилку" вибрати, тому він виправиться із повідомленням про помилку.
cwick

2
що насправді означає "пов'язаний лінкером"? всі використовують цей термін, я не знаходжу жодного визначення :(
Marcel Falliere

7
@MarcelFalliere Wiki ~ Компілятор збирає кожен вихідний файл самостійно і створює об'єктний файл для кожного вихідного файлу. Linker пов'язує ці об’єктні файли до 1 виконуваного файлу
Bitterblue

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

6
Чи не включає охоронець, що захищає від цієї конкретної речі?
obskyr

32

Вже було заявлено, що extern ключове слово є зайвим для функцій.

Що стосується змінних, поділених між одиницями компіляції, вам слід оголосити їх у файлі заголовка із ключовим словом extern, а потім визначити їх у єдиному вихідному файлі без ключового слова extern. Для найкращої практики єдиний вихідний файл повинен бути файлом, який надає ім'я файла заголовка.


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

Що робити, якщо ви не хочете відкривати жодну з функцій у файлі заголовка? Чи не було б краще оголосити змінну в одному файлі C і отримати доступ до неї зовнішнім в іншому; нехай лінк вирішить проблему та приховає решту заголовка.
ste3e

16

Через багато років я відкриваю це питання. Прочитавши кожну відповідь та коментар, я подумав, що можу уточнити кілька деталей ... Це може бути корисно для людей, які потрапляють сюди через пошук у Google.

Питання стосується конкретно використання функцій "extern", тому я ігнорую використання "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;

Важливі функції, які слід помітити:

  • Коли функція визначена як "статична" у файлі заголовка, компілятор / лінкер повинен знайти примірник функції з цим ім'ям у кожному модулі, який використовує файл, що включає цей файл.

  • Функція, що входить до бібліотеки С, може бути замінена лише в одному модулі шляхом повторного визначення прототипу з "статичним" лише в цьому модулі. Наприклад, замініть будь-який виклик на "malloc" і "free", щоб додати функцію виявлення витоку пам'яті.

  • Специфікатор "extern" дійсно не потрібен для функцій. Коли "статичний" не знайдено, функція завжди вважається "зовнішньою".

  • Однак "extern" не є типовим для змінних. Зазвичай будь-який файл заголовка, який визначає, що змінні повинні бути видимими для багатьох модулів, повинні використовувати "extern". Єдиним винятком буде, якщо гарантовано включення файлу заголовка з одного і лише одного модуля.

    Тоді багато менеджерів проектів вимагають, щоб така змінна була розміщена на початку модуля, а не в будь-якому файлі заголовка. Деякі великі проекти, такі як емулятор відеоігор "Mame", навіть вимагають, щоб така змінна з'явилася лише над першою функцією, що використовує їх.


Тож чому саме статичній функції потрібно визначення у порівнянні з зовнішнім? (Я знаю, що це запізнилося на 2 роки, але це насправді дуже корисно для розуміння)
SubLock69,

2
Визначення потрібне, якщо ви викликаєте функцію у рядку 100 та інстанціюєте її у рядку 500. Рядок 100 оголошує невизначений прототип. Отже, ви додаєте прототип біля верху.
Крістіан Гінграс

15

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

(Якщо потрібна статична зв'язок, функція повинна бути оголошена як "статична" як у прототипі, так і в заголовку функції, і вони, як правило, мають бути в одному файлі .c).


8

Дуже хороша стаття, яку я розповів про externключове слово, разом із прикладами: http://www.geeksforgeeks.org/understanding-extern-keyword-in-c/

Хоча я не згоден, що використання externу функціях декларацій є зайвим. Це має бути налаштування компілятора. Тому я рекомендую використовувати externв функції декларації, коли це потрібно.


3
Я прочитав статтю geeksforgeeks.org ще до того, як я прийшов сюди, але знайшов це досить погано написаним. Окрім граматичних та синтаксичних недоліків, вона використовує багато слів, щоб зробити кілька разів одну і ту ж точку, а потім проскакує критичну інформацію. Наприклад, у Прикладі 4 раптом включається 'somefile.h', але про нього нічого не сказано, окрім: "Припустимо, що деякий файл.h має визначення var". Що ж, інформація, яку ми "гадаємо", просто буває інформацією, яку я шукаю. На жаль, жодна відповідь на цій сторінці не набагато краща.
Elise van Looij

6

Якщо кожен файл у вашій програмі спочатку компілюється в об’єктний файл, то файли об’єктів пов’язані між собою, вам потрібно extern. Він повідомляє компілятору "Ця функція існує, але код для неї є деінде. Не панікуйте."


Гм, ось як зазвичай робиться переклад: вихідні файли збираються в об’єктні файли, а потім пов'язуються. Коли в цьому випадку вам не потрібен зовнішній вигляд? Також ви не використовуєте #include для отримання функцій, а скоріше прототипи функцій. Я не розумію, про що ти говориш.
Девід Торнлі

Здається, у мене останнім часом виникає ця проблема з неправильним читанням речей. Вибач за це. Коли я був новим для C, я б #include "file.c" просто включив функції одного файлу безпосередньо в інший файл. Тоді я придумав, як використовувати «екстерн». Я думав, що він робив ту саму помилку, що і я.
Кріс Лутц

4

Усі декларації функцій та змінних у заголовкових файлах повинні бути extern.

Виняток із цього правила - це вбудовані функції, визначені в заголовку, і змінні, які, хоча і визначені в заголовку, повинні бути локальними для блоку перекладу (вихідний файл, до якого заголовок потрапляє): це має бути static.

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

Єдиною причиною використання externвзагалі у вихідному файлі є оголошення функцій та змінних, які визначені в інших вихідних файлах і для яких не надається файл заголовка.


Оголошення прототипів функцій externнасправді непотрібне. Деяким людям це не подобається, оскільки вони просто витратять простір, а декларації функцій вже мають тенденцію до переповнення меж ліній. Іншим це подобається, тому що таким чином, функції та змінні можна трактувати однаково.


Чи можете ви навести причину, чому "Усі декларації функцій та змінних у заголовкових файлах повинні бути зовнішніми?"? З інших відповідей мені здається, що вони за замовчуванням зовнішні.
lillq

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

Хіба не найкраща ідея завжди включати глобальні змінні у файл C, щоб їх не бачили інші випадкові файли C, що містять заголовок. І завжди використовувати зовнішній вигляд на кожному глобальному, за винятком ініціалізованої справжньої раковини, для чіткості; якщо це префікс зовнішній, то він визначений в іншому місці.
ste3e

3

Функції, фактично визначені в інших вихідних файлах, повинні бути оголошені лише у заголовках. У цьому випадку вам слід використовувати extern при оголошенні прототипу в заголовку.

Здебільшого ваші функції будуть одним із наступних (більше як найкраща практика):

  • статичний (звичайні функції, які не видно поза цим файлом .c)
  • статичний вбудований (рядки з файлів .c або .h)
  • extern (декларація в заголовках наступного виду (див. нижче))
  • [жодного ключового слова немає] (звичайні функції мали бути доступними за допомогою зовнішніх декларацій)

Чому б ви поширювались, коли оголошували прототип, якщо це за замовчуванням?
lillq

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

1

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.