Урок по треку C++ 6 мин

Сборка и линковка

Единицы трансляции, препроцессор, ODR, inline, статические и динамические библиотеки, CMake.

Вопросы про сборку отличают того, кто писал на C++ проекты, от того, кто решал задачки. «Почему undefined reference», «что такое ODR» и «зачем inline» звучат на собесах middle и senior постоянно.

Путь от исходника до бинарника

  1. Препроцессор обрабатывает #include, #define, #if: вставляет текст заголовков, раскрывает макросы. Результат называется единицей трансляции (translation unit). g++ -E main.cpp покажет его.
  2. Компиляция превращает единицу трансляции в ассемблер, затем ассемблирование в объектный файл .o с машинным кодом, таблицей определённых символов и списком неразрешённых ссылок. g++ -c main.cpp.
  3. Линковка собирает объектные файлы и библиотеки, связывает ссылки с определениями и пишет исполняемый файл.

Каждый .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".

Закрепите за 5 минут

После урока в тренажёре 3-5 проверочных вопросов, дальше модуль уходит в повторения.