Одно правило определения в основном означает, что переменная / функция может быть расположена только в одном месте в адресном пространстве скомпилированного исполняемого файла. Один из способов думать об этом - во время компиляции есть массив памяти, который будет использоваться в скомпилированной программе (объектный код), и таблица поиска для ссылки на местоположения переменных / функций. Это делается на уровне процесса. Предположим, что следующая простая программа:
file1.cpp
int square(int x); // this is a declaration
extern int someVariable; // this is a declration
void square(int x) // this is a definition
{
return x * someVariable;
}
file2.cpp
int square(int x); // this is a declaration
int someVariable; // this is a definition
void main()
{
someVariable = 12;
someVariable = square(4);
}
Когда компилятор начинает компилировать объектный код, он читает объявления и помещает их в свою таблицу. В конце компиляции file1.cpp это закончится примерно так:
declarations:
square (XX): function that returns int, and takes a single int as parameter [4 bytes]
someVariable (YY): integer [4 bytes]
data:
12 34 56 78 aa XX XX XX XX ab cd
definition:
square: starts at address 0
Предполагается, что функция скомпилирована с этими конкретными инструкциями по сборке. Во время компоновки XX XX XX XX будет заменен адресом некоторой переменной.
Файл2 заканчивается примерно так:
declarations:
square (XX): function that returns int, and takes a single int as parameter [4 bytes]
someVariable (YY): integer [4 bytes]
data:
00 00 00 00 12 34 56 78 12 34 56 YY YY YY YY 23 21
definitions:
someVariable: starts at address 0
main: starts at address 4
И в этом случае YY будет заменен адресом квадрата.
Вот где в игру вступает компоновщик. Работа компоновщика состоит в том, чтобы просмотреть список и создать таблицу, в которой все находится в адресном пространстве программы во время компиляции. Однако существует проблема, если два объектных файла имеют одно и то же определение переменной при попытке связывания. Если бы в приведенном выше примере было два определения someVariable, то он не знал бы, чем заменить YY. Аналогично, если есть определение no , то вы получите ужасные ошибки компоновщика.
«Решением» правила является разбиение вашего файла таким образом, чтобы у вас были определения только в файлах .cpp, а в ваших файлах .h были объявления вещей, поэтому приведенный выше пример будет выглядеть так:
file1.cpp
#include "file2.h"
void square(int x) // this is a definition
{
return x * someVariable;
}
file1.h
int square(int x); // this is a declaration
file2.cpp
#include "file1.h"
int someVariable; // this is a definition
void main()
{
someVariable = 12;
someVariable = square(4);
}
file2.h
extern int someVariable;
Обратите внимание, что это невероятно простой пример, и что он на самом деле не применяется в .NET, поскольку отсутствует концепция различия между объявлением и определением.