Вопросы про сборку отличают того, кто писал на C++ проекты, от того, кто решал задачки. «Почему undefined reference», «что такое ODR» и «зачем inline» звучат на собесах middle и senior постоянно.
Путь от исходника до бинарника
- Препроцессор обрабатывает
#include,#define,#if: вставляет текст заголовков, раскрывает макросы. Результат называется единицей трансляции (translation unit).g++ -E main.cppпокажет его. - Компиляция превращает единицу трансляции в ассемблер, затем ассемблирование в объектный файл
.oс машинным кодом, таблицей определённых символов и списком неразрешённых ссылок.g++ -c main.cpp. - Линковка собирает объектные файлы и библиотеки, связывает ссылки с определениями и пишет исполняемый файл.
Каждый .cpp компилируется независимо, поэтому компилятору достаточно объявления функции, а определение найдёт линковщик.
Объявления, определения и ODR
One Definition Rule: у каждой переменной и не-inline функции в программе ровно одно определение. Нарушения:
- определение обычной функции в заголовке, подключённом в два
.cpp: «multiple definition»; - объявили, но не определили или не подключили объектный файл и библиотеку: «undefined reference».
Исключения: inline-функции и переменные (C++17), шаблоны, функции-члены, определённые внутри класса, можно определять в каждой единице трансляции, если определения одинаковы. Линковщик оставит одну копию. Если определения отличаются, это нарушение ODR без диагностики: UB, проявляется странными багами.
Связывание (linkage)
- Внешнее: символ виден из других единиц трансляции (обычные функции и глобальные переменные).
- Внутреннее:
staticна уровне файла или безымянное пространство имёнnamespace { ... }. У каждой единицы трансляции своя копия, конфликтов нет. extern "C"отключает декорирование имён (name mangling), чтобы функцию можно было вызвать из C или найти черезdlsym. Декорирование кодирует в имени символа пространства имён и типы параметров, на нём держится перегрузка.
Заголовки
- Защита от повторного включения:
#pragma onceили include guards#ifndef X_H / #define X_H / #endif. - Включайте то, что используете, и не больше: каждый лишний заголовок замедляет сборку всех зависимых файлов. Предварительные объявления (
class Foo;) вместо#include, где хватает указателя или ссылки.
Библиотеки
- Статическая (
.a,.lib): архив объектных файлов. Нужные объекты копируются в бинарник при линковке. Бинарник самодостаточен, но больше. - Динамическая (
.so,.dll,.dylib): загружается при запуске. Код общий для процессов, библиотеку можно обновить без пересборки, но нужна совместимость ABI, а поиск идёт поrpath,LD_LIBRARY_PATH, системным путям. - GNU ld просматривает статические библиотеки по порядку и берёт из архива только объекты, которые закрывают уже известные неразрешённые символы. Поэтому
g++ -lmath main.oдаст undefined reference, аg++ main.o -lmathнет: библиотеки пишут после тех, кто их использует.
Статическая инициализация
Порядок инициализации глобальных объектов из разных единиц трансляции не определён. Если глобальный объект в одном файле использует глобальный объект из другого, он может увидеть его неинициализированным: static initialization order fiasco. Лечится функцией с локальной статической переменной (Meyers singleton) или constinit/constexpr-инициализацией.
CMake
Современный CMake описывает цели и их свойства:
add_library(core STATIC core.cpp)
target_include_directories(core PUBLIC include)
target_link_libraries(app PRIVATE core)
PUBLIC передаёт зависимость тем, кто линкуется с целью, PRIVATE нет, INTERFACE только потребителям. Ускоряют сборку ccache, предкомпилированные заголовки, модули C++20 и unity-сборки.
Частые ошибки
- Определение не-inline функции или глобальной переменной в заголовке.
- Неправильный порядок
-lпри статической линковке. - Забыли
extern "C"при экспорте в C. - Зависимость глобальных объектов друг от друга между файлами.
- Разные флаги компиляции (например,
_GLIBCXX_DEBUG) в разных частях программы: нарушение ODR.
Что спросят на собесе
- Этапы сборки и что делает каждый.
- Чем объявление отличается от определения, что такое ODR.
- Откуда берутся undefined reference и multiple definition.
- Статические против динамических библиотек, порядок линковки.
- Что такое name mangling и
extern "C".