Ошибки компоновки и ODR
Заголовок triple.h под защитой #pragma once содержит не объявление, а определение функции: int triple(int x) { return x * 3; }. Его подключают два файла — report.cpp и вот этот main.cpp:
#include <iostream>
#include "triple.h" // ✗
int main() {
std::cout << triple(14) << '\n';
}Каждый файл компилируется без ошибок, но сборка clang++ main.cpp report.cpp останавливается на компоновке:
multiple definition of `triple(int)'Правило одного определения
Компоновщик собирает в программу объектные файлы всех единиц трансляции. В конторе это переплётчик: он сшивает папки всех отделов в одну книгу. Каждая из двух единиц получила из заголовка своё определение triple, и в книге оказались две страницы с телом одной функции. Компоновщик не выбирает между ними, а останавливает сборку.
Правило одного определения, сокращённо ODR, это запрещает: в программе не больше одного определения функции, а у вызванной — ровно одно. #pragma once здесь не поможет: отметка стоит на одной папке, а копии лежат в разных единицах трансляции.
Переключите, что лежит в triple.h, и посмотрите, на каком шаге останавливается сборка.
#pragma once int triple(int x) { return x * 3;}
#include <iostream>#include "triple.h" int main() { std::cout << triple(14) << '\n';}
#include "triple.h" int report() { return triple(2);}
#include <iostream>стандартный заголовок#include "triple.h"ждёт- int main() {
- std::cout << triple(14) << '\n';
- }
#include "triple.h"ждёт- int report() {
- return triple(2);
- }
Препроцессор идёт по строкам #include сверху вниз и вставляет вместо каждой текст файла. Нажмите кнопку.
Нарушить правило можно и с другой стороны. Если triple.h содержит только объявление, а triple.cpp не указали в команде сборки, все файлы скомпилируются: объявление есть в каждом. Тогда компоновщик сообщит undefined reference, а в задачах курса — undefined symbol. Он называет функцию, а не файл: найдите .cpp-файл с её определением и проверьте, что он есть в команде.
inline в заголовке
Если определение должно остаться в заголовке, функцию объявляют inline:
#pragma once
inline int triple(int x) {
return x * 3;
}Слово inline разрешает определению функции повторяться в разных единицах трансляции, если все копии одинаковы. Копии из одного заголовка одинаковы всегда, и компоновщик оставляет одну из них. Функции constexpr тоже считаются inline, поэтому их определяют прямо в заголовках. Остальные функции обычно делят: объявление — в заголовке, определение — в .cpp-файле.
Частая ошибка
Вместо заголовка подключили .cpp-файл:
#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-файл с решением, но там вся сборка — одна единица трансляции.
Главное
- Компоновщик собирает объектные файлы всех единиц трансляции в одну программу.
- По правилу одного определения у вызванной функции в программе ровно одно определение.
- Определение в заголовке без
inlineпопадает в каждую единицу и даётmultiple definition. - Забытый в команде сборки
.cpp-файл даётundefined reference, а в задачах курса —undefined symbol.
Подпишись, чтобы пройти курс
Курс бесплатный. Войди через Telegram и подпишись на канал — откроются все уроки.