Перейти к содержимому
Теория · Сборка и препроцессор · урок 5 из 7

Ошибки компоновки и ODR

Заголовок triple.h под защитой #pragma once содержит не объявление, а определение функции: int triple(int x) { return x * 3; }. Его подключают два файла — report.cpp и вот этот main.cpp:

C++
#include <iostream>
#include "triple.h"  // ✗

int main() {
    std::cout << triple(14) << '\n';
}

Каждый файл компилируется без ошибок, но сборка clang++ main.cpp report.cpp останавливается на компоновке:

TEXT
multiple definition of `triple(int)'

Правило одного определения

Компоновщик (linker) собирает в программу объектные файлы всех единиц трансляции. Каждая из двух единиц получила из заголовка своё определение triple, и в программе их стало два. Правило одного определения (one definition rule, ODR) это запрещает: в программе не больше одного определения функции, а у вызванной — ровно одно. #pragma once здесь не поможет: копии лежат в разных единицах трансляции.

Нарушить правило можно и с другой стороны. Если triple.h содержит только объявление, а triple.cpp не указали в команде сборки, все файлы скомпилируются: объявление есть в каждом. Тогда компоновщик сообщит undefined reference, а в задачах курса — undefined symbol. Он называет функцию, а не файл: найдите .cpp-файл с её определением и проверьте, что он есть в команде.

inline в заголовке

Если определение должно остаться в заголовке, функцию объявляют inline:

C++
#pragma once

inline int triple(int x) {
    return x * 3;
}

Слово inline разрешает определению функции повторяться в разных единицах трансляции, если все копии одинаковы. Копии из одного заголовка одинаковы всегда, и компоновщик оставляет одну из них. Функции constexpr тоже считаются inline, поэтому их определяют прямо в заголовках. Остальные функции обычно делят: объявление — в заголовке, определение — в .cpp-файле.

Частая ошибка

Вместо заголовка подключили .cpp-файл:

C++
#include <iostream>
#include "triple.cpp"  // ✗

int main() {
    std::cout << triple(14) << '\n';
}

Сам main.cpp компилируется, но сборка clang++ main.cpp triple.cpp снова заканчивается ошибкой multiple definition. Определение triple попало в две единицы трансляции: в triple.cpp и в main.cpp, куда его вставил #include. Подключайте заголовок triple.h, а .cpp-файлы передавайте только команде сборки. В задачах курса тесты тоже подключают .cpp-файл с решением, но там вся сборка — одна единица трансляции.