Мы уже знаем, что переменные, объявленные внутри блока, называются локальными. Они имеют локальную область видимости (используются только внутри блока, в котором объявлены) и автоматическую продолжительность жизни (создаются в точке определения и уничтожаются в конце блока).
Глобальными называются переменные, которые объявлены вне блока. Они имеют статическую продолжительность жизни, т.е. создаются при запуске программы и уничтожаются при её завершении. Глобальные переменные имеют глобальную область видимости (или «файловую область видимости»), т.е. их можно использовать в любом месте файла, после их объявления.
- Определение глобальных переменных
- Ключевые слова static и extern
- Предварительные объявления переменных с использованием extern
- Связи функций
- Файловая область видимости vs. Глобальная область видимости
- Глобальные символьные константы
- Предостережение о (неконстантных) глобальных переменных
- Заключение
- Тест
Определение глобальных переменных
Обычно глобальные переменные объявляют в верхней части кода, ниже директив #include, но выше любого другого кода. Например:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
#include <iostream> // Переменные, определенные вне блока, являются глобальными переменными int g_x; // глобальная переменная g_x const int g_y(3); // константная глобальная переменная g_y void doSomething() { // Глобальные переменные можно использовать в любом месте программы g_x = 4; std::cout << g_y << "\n"; } int main() { doSomething(); // Глобальные переменные можно использовать в любом месте программы g_x = 7; std::cout << g_y << "\n"; return 0; } |
Подобно тому, как переменные во внутреннем блоке скрывают переменные с теми же именами во внешнем блоке, локальные переменные скрывают глобальные переменные с одинаковыми именами внутри блока, в котором они определены. Однако с помощью оператора разрешения области видимости (::
), компилятору можно сообщить, какую версию переменной вы хотите использовать: глобальную или локальную. Например:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#include <iostream> int value(4); // глобальная переменная int main() { int value = 8; // эта переменная (локальная) скрывает значение глобальной переменной value++; // увеличивается локальная переменная value (не глобальная) ::value--; // уменьшается глобальная переменная value (не локальная) std::cout << "Global value: " << ::value << "\n"; std::cout << "Local value: " << value << "\n"; return 0; } // локальная переменная уничтожается |
Результат выполнения программы:
Global value: 3
Local value: 9
Использовать одинаковые имена для локальных и глобальных переменных — это прямой путь к проблемам и ошибкам, поэтому подобное делать не рекомендуется. Многие разработчики добавляют к глобальным переменным префикс g_
(«g» от англ. «global»). Таким образом, можно убить сразу двух зайцев: определить глобальные переменные и избежать конфликтов имен с локальными переменными.
Ключевые слова static и extern
В дополнение к области видимости и продолжительности жизни, переменные имеют еще одно свойство — связь. Связь переменной определяет, относятся ли несколько упоминаний одного идентификатора к одной и той же переменной или нет.
Переменная без связей — это переменная с локальной областью видимости, которая относится только к блоку, в котором она определена. Это обычные локальные переменные. Две переменные с одинаковыми именами, но определенные в разных функциях, не имеют никакой связи — каждая из них считается независимой единицей.
Переменная, имеющая внутренние связи, называется внутренней переменной (или «статической переменной»). Она может использоваться в любом месте файла, в котором определена, но не относится к чему-либо вне этого файла.
Переменная, имеющая внешние связи, называется внешней переменной. Она может использоваться как в файле, в котором определена, так и в других файлах.
Если вы хотите сделать глобальную переменную внутренней (которую можно использовать только внутри одного файла) — используйте ключевое слово static:
1 2 3 4 5 6 7 8 |
#include <iostream> static int g_x; // g_x - это статическая глобальная переменная, которую можно использовать только внутри этого файла int main() { return 0; } |
Аналогично, если вы хотите сделать глобальную переменную внешней (которую можно использовать в любом файле программы) — используйте ключевое слово extern:
1 2 3 4 5 6 7 8 |
#include <iostream> extern double g_y(9.8); // g_y - это внешняя глобальная переменная и её можно использовать и в других файлах программы int main() { return 0; } |
По умолчанию, неконстантные переменные, объявленные вне блока, считаются внешними. Однако константные переменные, объявленные вне блока, считаются внутренними.
Предварительные объявления переменных с использованием extern
Из урока №20 мы уже знаем, что для использования функций, которые определены в другом файле, нужно применять предварительные объявления.
Аналогично, чтобы использовать внешнюю глобальную переменную, которая была объявлена в другом файле, нужно записать предварительное объявление переменной с использованием ключевого слова extern
(без инициализируемого значения). Например:
global.cpp:
1 2 3 4 |
// Определяем две глобальные переменные int g_m; // неконстантные глобальные переменные имеют внешнюю связь по умолчанию int g_n(3); // неконстантные глобальные переменные имеют внешнюю связь по умолчанию // g_m и g_n можно использовать в любом месте этого файла |
main.cpp:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#include <iostream> extern int g_m; // предварительное объявление g_m. Теперь g_m можно использовать в любом месте этого файла int main() { extern int g_n; // предварительное объявление g_n. Теперь g_n можно использовать только внутри main() g_m = 4; std::cout << g_n; // должно вывести 3 return 0; } |
Если предварительное объявление находится вне блока, то оно применяется ко всему файлу. Если же внутри блока, то оно применяется только к нему.
Если переменная объявлена с помощью ключевого слова static
, то получить доступ к ней с помощью предварительного объявления не получится. Например:
constants.cpp:
1 |
static const double g_gravity(9.8); |
main.cpp:
1 2 3 4 5 6 7 8 9 |
#include <iostream> extern const double g_gravity; // не найдет g_gravity в constants.cpp, так как g_gravity является внутренней переменной int main() { std:: cout << g_gravity; // вызовет ошибку компиляции, так как переменная g_gravity не была определена для использования в main.cpp return 0; } |
Обратите внимание, если вы хотите объявить неинициализированную неконстантную глобальную переменную, то не используйте ключевое слово extern
, иначе C++ будет думать, что вы пытаетесь записать предварительное объявление.
Связи функций
Функции имеют такие же свойства связи, что и переменные. По умолчанию они имеют внешнюю связь, которую можно сменить на внутреннюю с помощью ключевого слова static
:
1 2 3 4 5 6 |
// Эта функция определена как static и может быть использована только внутри этого файла. // Попытки доступа к ней через прототип функции будут безуспешными static int add(int a, int b) { return a + b; } |
Предварительные объявления функций не нуждаются в ключевом слове extern
. Компилятор может определить сам (по телу функции): определяете ли вы функцию или пишете её прототип.
Файловая область видимости vs. Глобальная область видимости
Термины «файловая область видимости» и «глобальная область видимости», как правило, вызывают недоумение, и это отчасти объясняется их неофициальным использованием. В теории, в языке C++ все глобальные переменные имеют файловую область видимости. Однако, по факту, термин «файловая область видимости» чаще применяется к внутренним глобальным переменным, а «глобальная область видимости» — к внешним глобальным переменным.
Например, рассмотрим следующую программу:
global.cpp:
1 |
int g_y(3); // внешняя связь по умолчанию |
main.cpp:
1 2 3 4 5 6 7 8 9 10 |
#include <iostream> extern int g_y; // предварительное объявление g_y. Теперь g_y можно использовать в любом месте этого файла int main() { std::cout << g_y; // должно вывести 3 return 0; } |
Переменная g_y
имеет файловую область видимости внутри global.cpp. Доступ к этой переменной вне файла global.cpp отсутствует. Обратите внимание, хотя эта переменная и используется в main.cpp, сам main.cpp не видит её, он видит только предварительное объявление g_y
(которое также имеет файловую область видимости). Линкер отвечает за связывание определения g_y
в global.cpp с использованием g_y
в main.cpp.
Глобальные символьные константы
На уроке о символьных константах, мы определяли их следующим образом:
constants.h:
1 2 3 4 5 6 7 8 9 10 11 12 |
#ifndef CONSTANTS_H #define CONSTANTS_H // Определяем отдельное пространство имен для хранения констант namespace Constants { const double pi(3.14159); const double avogadro(6.0221413e23); const double my_gravity(9.2); // ... другие константы } #endif |
Хоть это просто и отлично подходит для небольших программ, но каждый раз, когда constants.h подключается в другой файл, каждая из этих переменных копируется в этот файл. Таким образом, если constants.h подключить в 20 различных файлов, то каждая из переменных продублируется 20 раз. Header guards не остановят это, так как они только предотвращают подключение заголовочного файла более одного раза в один файл. Дублирование переменных на самом деле не является проблемой (поскольку константы зачастую не занимают много памяти), но изменение значения одной константы потребует перекомпиляции каждого файла, в котором она используется, что может привести к большим временным затратам в более крупных проектах.
Избежать этой проблемы можно, превратив эти константы в константные глобальные переменные, и изменив заголовочный файл только для хранения предварительных объявлений переменных. Например:
constants.cpp:
1 2 3 4 5 6 7 |
namespace Constants { // Фактические глобальные переменные extern const double pi(3.14159); extern const double avogadro(6.0221413e23); extern const double my_gravity(9.2); } |
constants.h:
1 2 3 4 5 6 7 8 9 10 11 12 |
#ifndef CONSTANTS_H #define CONSTANTS_H namespace Constants { // Только предварительные объявления extern const double pi; extern const double avogadro; extern const double my_gravity; } #endif |
Их использование в коде остается неизменным:
1 2 3 4 5 |
#include "constants.h" //... double circumference = 2 * radius * Constants::pi; //... |
Теперь определение символьных констант выполняется только один раз (в constants.cpp). Любые изменения, сделанные в constants.cpp, потребуют перекомпиляции только (одного) этого файла.
Но есть и обратная сторона медали: такие константы больше не будут считаться константами типа compile-time и, поэтому, не смогут использоваться где-либо, где потребуется константа такого типа.
Поскольку глобальные символьные константы должны находиться в отдельном пространстве имен и быть доступными только для чтения, то использовать префикс g_
уже не обязательно.
Предостережение о (неконстантных) глобальных переменных
У начинающих программистов часто возникает соблазн использовать просто множество глобальных переменных, поскольку с ними легко работать, особенно когда задействовано много функций. Тем не менее, этого следует избегать! Почему? Об этом мы поговорим на следующем уроке.
Заключение
Подытожим вышесказанное:
Глобальные переменные имеют глобальную область видимости и могут использоваться в любом месте программы. Подобно функциям, вы должны использовать предварительные объявления (с ключевым словом extern
), чтобы использовать глобальную переменную, определенную в другом файле.
По умолчанию, глобальные неконстантные переменные имеют внешнюю связь. Вы можете использовать ключевое слово static
, чтобы сделать их внутренними.
По умолчанию, глобальные константные переменные имеют внутреннюю связь. Вы можете использовать ключевое слово extern
, чтобы сделать их внешними.
Используйте префикс g_
для идентификации ваших неконстантных глобальных переменных.
Тест
В чём разница между областью видимости, продолжительностью жизни и связью переменных? Какие типы продолжительности жизни, области видимости и связи имеют глобальные переменные?
Ответ
Область видимости определяет, где переменная доступна для использования. Продолжительность жизни определяет, где переменная создается и где уничтожается. Связь определяет, может ли переменная использоваться в другом файле или нет.
Глобальные переменные имеют глобальную область видимости (или «файловую область видимости»), что означает, что они доступны из точки объявления до конца файла, в котором объявлены.
Глобальные переменные имеют статическую продолжительность жизни, что означает, что они создаются при запуске программы и уничтожаются при её завершении.
Глобальные переменные могут иметь либо внутреннюю, либо внешнюю связь (это можно изменить через использование ключевых слов static
и extern
, соответственно).
Насчет пункта «Глобальные символьные константы» — проблема в том, что тот метод, который тут предоставлен, и вправду решает проблему, когда возможна перекомпиляция, к примеру, 20 файлов, как было описано, НО эти консанты на самом деле не константы времени компиляции (compile-time const), а времени запуска програмы (run-time const). И вправду — сначала они объявляются, а потом уже им присваиваются значения в .cpp-файле на этапе линкинга, но не компиляции, как требуется.
Тоесть такие константы не подойдут, например, для статических массивов (тоесть выделеных на стеке, а не динамически на куче).
Единственный способ проверить утверждение статичности константы — это взятие адреса данной переменной (пока не проходили).
Я написал простейшую программу, состоящую из 3-х файлов:
test_const.h
print_const.cpp
test_const.cpp
И получил ожидаемый результат:
Static const address:
1 [0x402004]
1 [0x402040]
Extern const address:
2 [0x402044]
2 [0x402044]
В первом случае (STATIC_CONST = 1) адреса получились разными. Это говорит о том, что при константы создавались во время построения объектного файла для каждого из файлов *.cpp.
Во втором случае (EXTERN_CONST = 2) адреса получились одинаковыми. Это говорит о том, что данная константа была создана только во время построения объектного файла для print_const.cpp, а затем использовалась ссылка на эту константу во время построения файла для test_const.cpp.
Здравствуйте
Я не совсем понял момент с копированием переменных 20 раз
что будет если сделать вот так?
не писать ключевое слово extern в constants.cpp
код скомпилируется, но как понять, создается множество копий или нет?
constants.cpp:
constants.h:
Не скомпилируется, так как без объявления extern для константных переменных не будет связи с твоим Constants.cpp
Возможно я что-то не так сделал, я уже не помню. Жаль, что не так быстро тут отвечают на комментарии)
Но у меня компилировалось. Я часть кода скопировал сюда как было.
Не внимательно читали предыдущие уроки, в каком точно не помню но этот механизм объясняли. Постараюсь ответить кратко про "копирование 20 раз":
Вот у вас есть файл constants.h в котором вы что то написали, везде в остальных файлах где вы напишите #include "constants.h", будет скопировано полностью содержимого этого файла.
Короче #include "constants.h" работает следующим образом, на этапе сборки, компилятор видит команду #include и затем имя файла в "" или <> скобках, он(компилятор) удаляет эту строку с командой и в это место где она была, он копирует ВСЕ содержимое того файла, имя которого там было написано.
Даже если у тебя программа состоит из одной строчки вывода сообщения
В верху файла присутствует строчка #include <iostream>, в котором тысячи строк. И по факту при компиляции, компилятор скопирует эти тысячи строк из того файла в твой, в то место где была строчка #include <iostream>. Вот так примерно это работает…
А как же volatile ?
Ну да, на 49 уроке только про многопоток и говорить
По поводу примера "Глобальные символьные константы". Имеет ли смысл создавать extern const double переменные в cpp файле, если можно оставить просто const double? Ведь extern уже определен в заголовочном файле.
Полезная тема. Никак не могла понять как сделать так, чтобы вводимое слово (в моем случае имя) отображалось в предложениях, которые в разных блоках. Оказалось, все так просто. Спасибо за перевод самоучителя.
Возникает вопрос в связи вот с чем:
"…Таким образом, если constants.h подключить в 20 различных файлов, то каждая из переменных продублируется 20 раз. Header guards не остановят это, так как они только предотвращают подключение заголовочного файла более одного раза в один файл. …"
Мы изучали, что директивы условной компиляции предотвращают или позволяют именно компиляцию по заданному условию. Из них и формируются Header guards.
Как тогда могут 20 раз дублироваться переменные, если компиляция возможна лишь раз? Дублируются эти 20 раз уже откомпилированные за первый раз переменные в виде машинного кода, или как-то еще?
Подключая заголовок.h к 20 различным файлам.cpp, в тем самым в каждый файл.cpp вставляете содержимое заголовков, всего получается что по одному разу на каждый файл.cpp, поэтому получается, что у вас создаться по 20 копий константных переменных, которые были в заголовке.h. Они не подключаются 20 раз в один файл, а по одному разу в каждый из 20 файлов. Как и написано в уроке, избежать этого можно написав в заголовке не определение, а объявление констант, то есть константы будут определены один раз в одном файле.cpp, а в заголовке будут определения. Так что меняя константы, вам придётся перекомпилировать лишь один файл.cpp, а всё остальное останется без изменений. Но в первом случае, вам бы пришлось перекомпилировать все 20 файлов.
Не могли бы Вы пояснить, в каких случаях применение extern является обязательным а в каких нет.
Добрый день! В данной главе сказано, что "если вы хотите сделать глобальную переменную внешней (которую можно использовать в любом файле программы) — используйте ключевое слово extern".. при этом ниже написано "По умолчанию, неконстантные переменные, объявленные вне блока, считаются внешними." Возникает недопонимание, обязательно ли в таком случаее вообще использовать extern?
Более того в "Уроке №20. Многофайловые программы" приводится пример с вызовом функции из другого файла без extern, и в данной главе сказано, что "Функции имеют такие же свойства связи, что и переменные…", в связи с чем также складывается впечатление, что использование extern с глобальными переменными необязательно.
В предыдущих уроках, мы подключали сам заголовочный файл h или cpp в исходный файл, здесь ты это делаешь без подключения заголовочных файлов.
Здарова!
Уже не первый раз обращаюсь именно к этому уроку, с целью перечитать про глобальные переменные.
Мне кажется было-бы круто, в название урока добавить например extern, через запятую после основного названия.
Потому, что ищешь поиском по странице с содержанием и не находишь. Думаю я не первый и не последний такой :))
А так все круто!
PS: О! Ща куплю твою книгу!
Привет, чуть позже добавлю оглавление.
Здравствуйте. Возник вопрос, в заголовке "Файловая область видимости vs глобальная область видимости" говорится, что такая переменная не будет использоваться во всех файлах. Почему?Она получила свое определение в одном файле, не константная, следовательно, имеет видимость во всех файлах. А в файле main.cpp она предварительно объявляется с помощью extern?
Спасибо большое!