Когда использовать extern в C ++

400

Я читаю "Думай в C ++", и он только что представил extern объявление. Например:

extern int x;
extern float y;

Я думаю, что понимаю значение (декларация без определения), но мне интересно, когда это окажется полезным.

Может кто-нибудь привести пример?

Aslan986
источник
1
Я должен был дать определение externв нескольких случаях. Инструменты Microsoft создали ошибку ссылки для отсутствующих символов, когда таблицы в другом исходном файле были определены только. Проблема была в том, что таблица была, constи компилятор C ++ сделал это staticв модуле перевода. Смотри, например, ariatab.cppи kalynatab.cpp.
17
2
И я думаю, что ответ Ника правильный, потому что он единственный, кто, кажется, ответил на вопрос C ++. Все остальные, кажется, отклонились от вопроса C.
17

Ответы:

521

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

Чтобы уточнить, использование extern int x;сообщает компилятору, что объект с intименем типа xсуществует где-то . Это не работа компилятора, чтобы знать, где он существует, просто нужно знать тип и имя, чтобы он знал, как его использовать. Как только все исходные файлы скомпилированы, компоновщик разрешит все ссылки xна одно определение, которое он найдет в одном из скомпилированных исходных файлов. Чтобы это работало, определение xпеременной должно иметь то, что называется «внешней связью», что в основном означает, что она должна быть объявлена ​​вне функции (в том, что обычно называется «областью действия файла») и без staticключевого слова.

заголовок:

#ifndef HEADER_H
#define HEADER_H

// any source file that includes this will be able to use "global_x"
extern int global_x;

void print_global_x();

#endif

источник 1:

#include "header.h"

// since global_x still needs to be defined somewhere,
// we define it (for example) in this source file
int global_x;

int main()
{
    //set global_x here:
    global_x = 5;

    print_global_x();
}

источник 2:

#include <iostream>
#include "header.h"

void print_global_x()
{
    //print global_x here:
    std::cout << global_x << std::endl;
}
dreamlax
источник
15
Спасибо. Итак, если я объявляю глобальную переменную в заголовочном файле без ключевого слова extern, исходные файлы, содержащие заголовок, не видят его?
Aslan986
23
Вы не должны объявлять глобальные переменные в заголовке, потому что тогда, когда 2 файла содержат один и тот же заголовочный файл, он не будет связываться (компоновщик выдаст ошибку об «дублированном символе»)
Куба
63
@ Aslan986: Нет, что-то хуже происходит. Каждый исходный файл, содержащий заголовок, будет иметь свою собственную переменную, поэтому каждый исходный файл будет компилироваться независимо, но компоновщик будет жаловаться, потому что два исходных файла будут иметь одинаковые глобальные идентификаторы.
Dreamlax
7
Если вы не используете слово «extern», тогда переменная существует. Когда вы используете «extern», это «эй, есть этот вар где-то еще». Извините, что не отвечаю, является ли это определением или объявлением, так как я всегда запутываюсь в этих двух.
Куба
3
@CCJ: include guard работает только для исходного файла, который его включает. Он останавливает включение одного и того же заголовка дважды в один и тот же исходный файл (на случай, если другие заголовки также включают его и т. Д.). Таким образом, даже с включенной защитой, каждый исходный файл, который включает заголовок, все равно будет иметь свое собственное определение.
сонный сон
172

Это полезно, когда вы разделяете переменную между несколькими модулями. Вы определяете это в одном модуле, и используете extern в других.

Например:

в file1.cpp:

int global_int = 1;

в file2.cpp:

extern int global_int;
//in some function
cout << "global_int = " << global_int;
MByD
источник
39
Этот ответ является более правильным, чем принятый, поскольку он не использует заголовочный файл и четко указывает, что он полезен только при совместном использовании несколькими модулями. Для более крупных приложений лучше использовать, например, класс ConfigManager.
Зак
1
Есть ли какие-то ошибки, когда задействованы пространства имен, global_intв глобальном пространстве имен, если бы я использовал его в file2.cpp в каком-то разделе пространства имен, я должен был бы охватить это правильно? то естьnamespace XYZ{ void foo(){ ::global_int++ } };
jxramos
8
@Zac: С другой стороны, не объявляя глобальную переменную в заголовке, вы случайно усложнили определение места ее определения. Обычно, если вы видите глобальную переменную, объявленную в abc.h, есть большая вероятность, что она будет определена в abc.cpp. Хорошая IDE всегда поможет, но хорошо организованный код - всегда лучшее решение.
Dreamlax
без externв file2.cpp, все еще может получить доступ global_intпосле включения. зачем мне это надо?
TomSawyer
62

Это все о связи .

Предыдущие ответы дали хорошие объяснения о extern.

Но я хочу добавить важный момент.

Вы спрашиваете externв С ++ не в C , и я не знаю , почему нет ответа упоминать о том случае , когда externприходит с constв C ++.

В C ++ constпеременная по умолчанию имеет внутреннюю связь (не как C).

Так что этот сценарий приведет к ошибке компоновки :

Источник 1:

const int global = 255; //wrong way to make a definition of global const variable in C++

Источник 2:

extern const int global; //declaration

Это должно быть так:

Источник 1:

extern const int global = 255; //a definition of global const variable in C++

Источник 2:

extern const int global; //declaration
Тревор
источник
2
Почему это неправильно, когда он работает в c ++ без включения 'extern' в часть определения?
Главный Шифтер
1
Кажется, я не сталкивался с этой ошибкой компоновки в VIsual Studio с Visual Micro. Что мне не хватает?
Craig.Feied
1
@ lartist93 @ Craig.Feied Я думаю, тебе, возможно, придется еще раз внимательно проверить. Даже если компилятор не сообщает об ошибке компоновки, можете ли вы проверить, что оба объекта в обоих источниках одинаковы без externопределения? Вы можете сделать это, распечатав значение globalв источнике 2.
Тревор
3
Подтвердите, в МСВСЕ 2018 года там есть связующая ошибка , если externопущен в const int global = 255;.
Evg
13

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

Marlon
источник
В любом случае это звучит не очень ООП, я бы поместил их в одноэлементный класс ... или функцию, возвращающую локальное статическое значение ...
RzR