Avatar ·

В чем разница между объявлением и определением в C++?

Привет! Я недавно начал изучать C++ и постоянно путаюсь в терминах «объявление» (declaration) и «определение» (definition). Вроде бы звучит похоже, но компилятор ругается на разные вещи. Вот пример, который меня смутил. Я написал такой код: ```cpp #include int x; // Это объявление или определение? extern int y; // А это? void foo(); // А тут? int main() { std::cout << x; return 0; } void foo() { // какая-то реализация } ``` Если я уберу `void foo() { ... }` и оставлю только `void foo();`, то компилятор выдаст ошибку линковки. Но если я напишу `int x;` без `extern`, то всё работает. Почему так? В каких случаях `int x;` — это объявление, а в каких — определение? И что такое ODR (One Definition Rule)? Объясните, пожалуйста, на пальцах, с примерами, чтобы я больше не путался. <<>>

Ключевое отличие: объявление сообщает компилятору «что-то существует», а определение — «вот как это выглядит»

Разница между объявлением и определением — одна из фундаментальных концепций C++, которую нужно понять, чтобы не получать ошибки линковки. Давайте разберем на примерах.

1. Объявление (Declaration)

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

extern int x;      // Объявление: "Где-то есть int x"
void foo();        // Объявление: "Где-то есть функция foo"
class MyClass;     // Объявление (предварительное): "Где-то есть класс MyClass"

Ключевое слово extern явно говорит: «это объявление, определение ищи в другом месте». Для функций extern необязателен — void foo(); всегда является объявлением, если после него нет тела.

2. Определение (Definition)

Определение реализует объявленную сущность: выделяет память (для переменных) или создает тело (для функций). Оно говорит компилятору: «Вот конкретный объект/функция».

int x = 5;         // Определение: выделили память под int x
void foo() { }     // Определение: создали тело функции
class MyClass { }; // Определение: полностью описали класс

3. Подводный камень: int x; без extern

В глобальной области видимости int x; — это определение (так называемое «tentative definition» в C, но в C++ это полноценное определение с value-initialization). Поэтому:

  • int x; — определение (память выделена, x инициализирован нулем)
  • extern int x; — объявление (память не выделена, ссылается на определение где-то еще)

4. Почему возникает ошибка линковки?

Если вы пишете только void foo(); (объявление), но не предоставляете определение void foo() { ... }, то линковщик не найдет реализацию и выдаст ошибку undefined reference. Правило ODR (One Definition Rule) гласит: у каждой сущности должно быть ровно одно определение во всей программе (или в одной единице трансляции для inline-сущностей).

5. Примеры для закрепления

// Пример 1: Правильный код
int a;           // Определение
extern int b;    // Объявление
int b = 10;      // Определение b (должно быть в другой единице трансляции, иначе ошибка)

// Пример 2: Ошибка линковки
void print();    // Объявление
int main() {
    print();     // Линковщик: "Где определение print()?"
    return 0;
}
// Определение print() отсутствует -> ошибка

// Пример 3: Два определения одной переменной
int x = 1;
int x = 2;      // Ошибка: multiple definition of 'x'

Итог

  • Объявление: «имя существует, вот его тип». Не выделяет память.
  • Определение: «вот сам объект/функция». Выделяет память/создает код.
  • Для переменных: extern = объявление, без extern = определение.
  • Для функций: прототип = объявление, тело = определение.
  • Нарушение ODR (два определения) — ошибка компиляции или линковки.
<<>>

Дополнение: когда объявление и определение могут совпадать

В некоторых контекстах граница стирается. Например:

  • Локальные переменные: int x; внутри функции — это всегда определение (память на стеке выделяется).
  • Классы внутри класса: static const int x = 5; — это объявление (не выделяет память до тех пор, пока не будет определено вне класса), но при этом задает значение.
  • Inline-функции и переменные (C++17): inline int x = 5; — это определение, но может повторяться в разных единицах трансляции без нарушения ODR.

Практический совет

Если вы видите ошибку «undefined reference» — значит, вы объявили что-то, но забыли определить. Если «multiple definition» — вы определили что-то дважды. Используйте extern для объявлений в заголовочных файлах, а определения помещайте в .cpp файлы. Для inline-функций и шаблонов определения можно оставлять в заголовках.