translation unit - файл, прошедший предпроцессинг и готовый к компиляции
4 стадии сборки: препроцессинг компиляция ассемблирование линковка
-
препроцессинг (.i / .ii). обрабатываются директивы препроцессора (инклуд, дефайн, прагма), удаляются комментарии <> нужно искать по стандартным путям "" искать там где main.c а также по путям которые даны в параметрах запуска инклуды копипастятся в файл
-
компиляция (.s). превращает код на си/c++ в код на ассемблере 2.1 лексический анализ: исходный код разбивается на токены 2.2 синтаксический анализ: построение AST, проверка грамматики что все скобки и ; стоят 2.3 семантический анализ: проверка типов и скоупов. 2.4 преобразование в платформонезависимый код (например в LLVM IR) 2.5 оптимизация: удаление неиспользуемого кода, свертка констант, инлайнинг функций 2.6 генерация ассемблера для целевой архитектуры
-
ассемблирование (.obj / .o). код на ассемблере переводится в машинный код .obj/.o файлы отличаются от исполняемых тем, что там могут стоять заглушки для вызова функций
-
линковка (.exe / .out) связывает все объектные файлы и статические библиотеки в единый исполняемый файл, ну и external linkage разрешает ошибки линковщика: undefined reference to function
Библиотеки бывают двух видов — статические и динамические. Код первых при компиляции полностью входит в состав исполняемого файла, что делает программу легко переносимой. Код динамических библиотек не входит в исполняемый файл, последний содержит лишь ссылку на библиотеку. Если динамическая библиотека будет удалена или перемещена в другое место, то программа работать не будет. С другой стороны, использование динамических библиотек позволяет сократить размер исполняемого файла. Также если в памяти находится две программы, использующие одну и туже динамическую библиотеку, то последняя будет загружена в память лишь единожды.
в с++ можно подключать как <stdio.h>, так и <cstdio>, но принято сишные хедеры предворять префиксом c
перегрузка функций:
этап компиляции:
- перегрузка шаблонов
- инстанцирование шаблонов, выбор специализации
- набор кандидатов (выбор тех, кто виден)
- overloading resolution (из набранных кандидатов выбор лучшего)
- проверка доступа (приватность)
runtime:
выбор версии виртуальной функции
К высокоуровневым системам сборки относятся Autoconf, Cmake, Meson, к низкоуровневым — Make, Ninja. Майкрософтская система сборки: MSBuilt
ODR
ODR - каждая сущность в программе должна быть ровно один раз определена ODR для классов работает по другому. класс может быть определен несколько раз во всей программе, при том условии, что все определении идентичны если определить классы по разному, то это UB определять класс несколько раз в пределах одного translation unit нельзя
почему мы не пишем определения функций в хедерах? потому что тогда бы заинклудив этот хедер во много translation unit’ов мы бы получили multiple definition одной и той же функции поэтому мы пишем определения функций в .cpp, чтоб потом линковщик связал это определение со всеми вызовами
зачем вообще нужны хедеры? чтоб разделять на объявления и определения засчёт того, что мы инклудим только объявления, а определения лежат где-то в другом месте, то мы можем не перекомпилировать определения каждый раз то есть к ним мы только линкуемся и не перекомпилируем
то есть стандартная библиотека лежит уже скомпилированная в ошниках (.o) на компах и линковщик знает, в каком объектном файле искать определение
шаблонные функции зачастую определяются в хедерах, потому что если бы все шаблонные определения были в .cpp, то непонятно как его отдельно компилировать, какие типы подставлять когда мы их используем в .cpp компилятор сам истанцирует специализации должны определяться в .cpp, тк multiple definitions
функции, которые возвращают auto должны быть определены сразу за объявлением, тк компилятор иначе не поймёт возвращаемый тип
extern A a; - объявление
A a; - объявление + определение
для переменных extern означает, что это не определение, а объявление, а определение пусть где-то в другом файле ищет, где будет написано глобальноA a = {2};
linkage
в c++ именам соответствуют сущности (класс, функция, переменная, неймспейс) у имён linkage (отсутствие, internal, external, module (C++20)), а у объектов есть storage duration (автоматическая, статическая, динамическая, thread-local) и lifetime
storage duration - это свойство объекта, определяющее время жизни памяти, которая выделяется под этот объект. другими словами правила, по которым компилятор управляет памятью для объекта
-
static: память под объект живёт на протяжении всего выполнения программы. переменная имеет static storage duration если она в глобальном скоупе, в неймспейсовом скоупе или где угодно, если она static/extern у переменных с static duration может быть статическая инициализация или динамическая. сначала static initialization: нелокальные переменные инициализируются константным экспрешеном (constant initialization) до старта программы. иначе иниализируются нулями. после dynamic initialization: для тех, кто требует вызов нетривиального конструктора или вызова функции не гарантируется порядок dynamic_initialization если переменная с static/thread_local storage duration зависит от ещё неинициализированной такой же переменной, то это UB. нужно написать constinit, чтоб у неё была static initialization
-
automatic: память под объект живёт от начала до конца блока, в котором они объявлены, реализуется через стек программы. переменная имеет automatic storage duration если она в блок скоупе (локальная) или в скоупе параметров (параметры функции) каждый control statement (if/switch/for/while), try/catch, функция имеет блок скоуп просто { } это тоже блок скоуп
-
dynamic: память вручную выделяется от выделения до удаления. переменная имеет dynamic storage duration если была создана с помощью new/malloc/placement new и до того, как память освободится delete/free. при placement new storage duration и lifetime может быть разный, lifetime может быть короче, если память затрётся. эксепшены имеют dynamic storage duration lifetime - совокупность моментов времени, когда её состояние валидно. объявление не начало lifetime! время жизни начинается после инициализации int x = x; - UB, используем переменную до её рождения, чтение неинициализированной памяти
-
thread_local: на каждый поток выделяется своя память под объект и она живёт на протяжении всего потока. переменная имеет thread_local storage duration, если она объявлена с thread_local спецификатором static initialization: инициализируются в compile-time, если заранее известно значение dynamic initialization: инициализируются при создании потока
Связывание linkage – это свойство идентификатора, позволяющее компилятору в некоторых случаях создавать для нескольких одинаковых имен, объявленных в разных единицах трансляции, одну общую сущность. Вместе с областью видимости связывание определяет, из каких единиц трансляции и их блоков можно обратиться к сущности.
если есть имя в некотором скоупе, которое может быть использовано из другого скоупа, то тогда у этого имени есть связывание. если к имени можно обратиться только из скоупа, где оно было объявлено, то у него нет связывания (локальные переменные)
-
no linkage: для линковщика их не существует, он о них не знает, если не написан extern
-
external linkage: “то, что торчит наружу из translation unit’a” глобальные переменные, классы, объявления глобально или внутри неймспейса, функции, статические поля и все методы классов - линковщик их видит как имя, торчащее наружу они все если объявлены, но не инициализированы, инициализируются по дефолту: obj{} для всех имён с external linkage есть ещё lanuage linkage - C-linkage (для коннекта с другими языками) и C++-linkage
-
internal linkage: имена, которые доступны между скоупами, но только в одном translation unit’е чтоб сделать глобальную переменную или функцию с internal linkage вместо external linkage, нужно написать static если написать static для глобальной функции/переменной или функции/переменной в неймспейсе, то к ней можно обращаться из других областей видимости, но только в пределах одного translation unit’a. для линковщика этот символ будет линковаться, но не между translation unit’ами у глобальных констант internal linkage, т.е. глобальная константа не то же самое, что глобальная переменная если глобальная константа написана в хедере, то для каждого TU она будет своя константное static поле класса тоже имеет internal linkage чтоб сделать класс с internal linkage, нужно ему написать анонимный неймспейс (C++11)
namespace {
struct S {};
}
/* если так написать в хедере, то будет аналогично
struct S {};, а если в .cpp, то эта "глобальная" структура не будет просачиваться в другие .cpp
таким образом можно сделать несколько структур с одинаковыми именами, но разным определением в разных .cpp
если сделать extern в анонимном неймспейсе, то будет всё равно internal linkage */есть ещё external weak linkage, но это не является частью стандарта C++, а являются терминами линковщика для линковщика бывает strong и weak external linkage пример weak linkage - оператор new помечен макросом для линковщика weak linkage - когда линковщих берёт первое попавшееся определение и игнорирует все остальные
в этих 3 случаях static означает разное
- static для локальных переменных влияет на их storage duration и у них полюбому no linkage потому что они локальные
- static для переменных в классовом скоупе делает её external linkage, то есть доступной из других скоупов и translation unit’ов
- static для глобальных переменных или переменных в неймспейсах делает их internal linkage, то есть доступными лишь в одном этом translation unit’е
inline
исходный смысл inline - просьба компилятору попробовать встроить тело этой функции прям в ассемблер, без её вызова сейчас оно устарело и не рекомендуется к использованию
побочный эффект inline: если функция inline, то для неё должно быть разрешено нарушать ODR, то есть по задумке она не должна дойти до линкера. но даже если она дойдёт до линкера, то ей будет прощаться нарушение ODR 3 главные применения inline:
- объявление специализации шаблона в хедере, чтоб не было multiple definition
- чтоб определить статические поля класса внутри класса (если бы можно было так определять, то multiple definition из-за external linkage), но если написать inline (C++17), то можно будет
- чтоб в хедере объявить глобальную константу и чтоб она была с external (weak) linkage и была создана всего один раз
если функция constexpr, то она автоматически inline errors