March 14th, 2020

C++: правило одного определения, extern

Продолжение, начало тут:
1. 13 глава Лафоре, обязательность main, С++;
2. С++: объявление и определение переменной.

В C++ существует правило одного определения (по-английски «One Definition Rule», сокращенно «ODR»):
https://ru.wikipedia.org/wiki/Правило_одного_определения

Это правило говорит о том, что в программе не должно быть двух и более определений одного и того же программного элемента (переменной, функции, класса). Например, следующий код (компилятор среды «Visual Studio Community 2017»):
int someVar;
int someVar; // ошибка
послужит причиной ошибки при компиляции:
error C2086: int someVar: переопределение

В программе из одного файла это правило понятно и очевидно. В случае программы из нескольких файлов ситуация усложняется. В многофайловой программе часто требуется наладить межфайловое взаимодействие. Что это такое? Например, может потребоваться возможность работать в разных файлах одной программы с одной и той же переменной. Такую переменную можно назвать межфайловой.

Очевидно, что межфайловая переменная должна быть глобальной, так как область видимости локальной переменной ограничена, к примеру, телом одной функции (или каким-либо другим блоком).

Попытаемся наладить межфайловое взаимодействие через переменную на примере. У нас есть проект из двух файлов. В одном файле определим и инициализируем переменную:
// file_A.cpp
int someVar = 3;
В другом файле попытаемся ее использовать:
// file_B.cpp
#include <iostream>
using namespace std;
int main()
{
    someVar = someVar + 7;   // ошибка
    cout << someVar << endl; // ошибка
}
При сборке этого проекта получим ошибку:
error C2065: someVar: необъявленный идентификатор

Попробуем определить нашу переменную и во втором файле, чтобы ее можно было использовать:
// file_A.cpp
int someVar = 3;
// file_B.cpp
#include <iostream>
using namespace std;

int someVar; // ошибка

int main()
{
    someVar = someVar + 7;
    cout << someVar << endl;
}
и при сборке проекта снова получим ошибку, но теперь другую:
error LNK2005: "int someVar" уже определен в file_A.obj

Это и есть нарушение рассмотренного выше правила одного определения для случая многофайловой программы.

Кажется, что тут есть некий логический парадокс: в первый раз при сборке проекта из двух файлов среда программирования заявляет, что, вроде как, не видит переменную someVar. А при второй сборке среда сообщает, что эта переменная уже определена в другом файле!

На самом деле, логика тут есть. Сборка состоит из двух этапов: 1) компиляция (трансляция) в объектный файл каждого исходного файла; 2) компоновка из объектных файлов окончательного исполняемого файла. На первом этапе компилятор «видит» в пределах лишь одного файла, того, который транслируется в данный момент. На втором этапе компилятор «видит» содержимое всех файлов. У нас так получилось, что в первый раз ошибка была найдена на первом этапе сборки, а во второй раз — на втором этапе.

Что же делать, чтобы межфайловое взаимодействие начало работать? Нужно, чтобы не было нарушения правила одного определения. Для этого необходимо во втором файле нашу переменную не определить, а объявить, так как множественные объявления в C++ не запрещены. В предыдущем посте упоминалось, что в большинстве случаев объявление переменной одновременно является и ее определением. Чтобы сделать только объявление ранее определенной переменной, применяется служебное слово extern:
// file_A.cpp
int someVar = 3; // объявление, определение, инициализация
// file_B.cpp
#include <iostream>
using namespace std;

extern int someVar; // только объявление

int main()
{
    someVar = someVar + 7;
    cout << someVar << endl;
}
Этот код у меня собирается и работает без ошибок. На экран выводится число 10.

extern переводится на русский как «внешний». Для нашего примера имеется в виду, что определение переменной размещено в другом (внешнем по отношению к текущему) файле (единице трансляции), а здесь только ее объявление.

Вообще это служебное слово применяется и для других целей:
https://docs.microsoft.com/en-us/cpp/cpp/extern-cpp
https://en.cppreference.com/w/cpp/keyword/extern