6.6 – Внутреннее связывание

Добавлено 11 мая 2021 в 13:58

В уроке «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 – Обзор области видимости, продолжительности и связывания».

Теги

C++ / CppLearnCppstaticВнутреннее связываниеГлобальная переменнаяДля начинающихОбучениеПрограммирование

На сайте работает сервис комментирования DISQUS, который позволяет вам оставлять комментарии на множестве сайтов, имея лишь один аккаунт на Disqus.com.

В случае комментирования в качестве гостя (без регистрации на disqus.com) для публикации комментария требуется время на премодерацию.