6.6 – Внутреннее связывание
В уроке «6.3 – Локальные переменные» мы сказали: «Связывание идентификатора определяет, относятся ли другие объявления с этим же именем к тому же объекту или нет», и мы обсудили, что локальные переменные не имеют связывания.
Идентификаторы глобальных переменных и функций могут иметь внутреннее или внешнее связывание. В этом уроке мы рассмотрим случай внутреннего связывания, а в уроке «6.7 – Внешнее связывание» – случай внешнего связывания.
Идентификатор с внутренним связыванием можно видеть и использовать в одном файле, но он недоступен из других файлов (то есть не предоставляется компоновщику). Это означает, что если два файла имеют идентификаторы с одинаковыми именами и внутренним связыванием, эти идентификаторы будут рассматриваться как независимые.
Глобальные переменные с внутренним связыванием
Глобальные переменные с внутренним связыванием иногда называют внутренними переменными.
Чтобы сделать неконстантную глобальную переменную внутренней, мы используем ключевое слово static
.
static int g_x; // неконстантные глобальные переменные по умолчанию имеют внешнее связывание,
// но с помощью ключевого слова static могут получить внутреннее связывание
const int g_y { 1 }; // глобальные переменные const по умолчанию имеют внутреннее связывание
constexpr int g_z { 2 }; // глобальные переменные constexpr по умолчанию имеют внутреннее связывание
int main()
{
return 0;
}
Глобальные переменные const
и constexpr
по умолчанию имеют внутреннее связывание (и, следовательно, не нуждаются в ключевом слове static
– если оно используется, то оно будет проигнорировано).
Вот пример проекта из нескольких файлов с внутренними переменными:
a.cpp:
constexpr int g_x { 2 }; // это внутренняя переменная g_x доступна только в a.cpp
main.cpp:
#include <iostream>
static int g_x { 3 }; // это отдельная внутренняя переменная g_x
// доступна только в main.cpp
int main()
{
std::cout << g_x << '\n'; // использует g_x из main.cpp, печатает 3
return 0;
}
Эта программа напечатает:
3
Поскольку g_x
является внутренней переменной для каждого файла, main.cpp не знает, что a.cpp также имеет переменную с именем g_x
(и наоборот).
Для продвинутых читателей
Использование ключевого слова static
выше является примером спецификатора класса хранения, который устанавливает как связывание имени, так и продолжительность его хранения (но не область его действия). Наиболее часто используемые спецификаторы класса хранения – это static
, extern
и mutable
. Термин «спецификатор класса хранения» в основном используется в технической документации.
Правило одного определения и внутреннее связывание
В уроке «2.6 – Предварительные объявления и определения» мы отметили, что правило одного определения гласит, что объект или функция не может иметь более одного определения ни в файле, ни в программе.
Однако стоит отметить, что внутренние объекты (и функции), которые определены в разных файлах, считаются независимыми объектами (даже если их имена и типы идентичны), поэтому здесь нет нарушения правила одного определения. Каждый внутренний объект имеет только одно определение.
Функции с внутренним связыванием
Поскольку связывание является свойством идентификатора (а не переменной), идентификаторы функций имеют такое же свойство связывания, что и идентификаторы переменных. Функции по умолчанию устанавливают внешнее связывание (которое мы рассмотрим в следующем уроке), но с помощью ключевого слова static
их можно настроить на внутреннее связывание:
add.cpp:
// Эта функция объявлена как статическая и теперь может использоваться только в этом файле.
// Попытки получить к нему доступ из другого файла через предварительное объявление функции
// завершатся ошибкой.
static int add(int x, int y)
{
return x + y;
}
main.cpp:
#include <iostream>
int add(int x, int y); // предварительное объявление для функции add
int main()
{
std::cout << add(3, 4) << '\n';
return 0;
}
Эта программа не будет линковаться, потому что функция add
недоступна за пределами add.cpp. И в итоге компоновщик выдаст ошибку.
Краткое резюме
// Определения внутренних глобальных переменных:
static int g_x; // определяет неинициализированную внутреннюю глобальную переменную
// (по умолчанию инициализируется нулем)
static int g_x{ 1 }; // определяет инициализированную внутреннюю глобальную переменную
const int g_y { 2 }; // определяет инициализированную внутреннюю глобальную переменную const
constexpr int g_y { 3 }; // определяет инициализированную внутреннюю глобальную переменную constexpr
// Определения внутренних функций:
static int foo() {}; // определяет внутреннюю функцию
Исчерпывающее резюме мы дадим в уроке «6.11 – Обзор области видимости, продолжительности и связывания».