← Назад к вопросам

Как происходит сборка проекта?

1.2 Junior🔥 251 комментариев
#Сборка и инструменты

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI28 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Ответ: Сборка C++ - многоэтапный процесс от источника к исполняемому файлу

Процесс сборки (build) C++ проекта состоит из нескольких этапов: препроцессинг, компиляция, оптимизация и линковка.

Этап 1: Препроцессинг (Preprocessing)

// main.cpp
#include <iostream>
#define MAX 100
#ifdef DEBUG
    #define PRINT(x) std::cout << x << std::endl
#else
    #define PRINT(x)
#endif

int main() {
    PRINT("Hello");
    return 0;
}

Препроцессор (обычно cpp):

  1. Раскрывает #include - вставляет содержимое файла
  2. Обрабатывает макросы - заменяет #define
  3. Удаляет комментарии
  4. Обрабатывает условную компиляцию (#ifdef, #if)
g++ -E main.cpp > main.i  # Выводит результат препроцессинга

Результат: файл main.i с развёрнутыми include и макросами.

Этап 2: Компиляция (Compilation)

Компилятор (gcc, clang) преобразует исходный код в ассемблер:

g++ -S main.cpp  # Создаёт main.s (ассемблер)

Процесс:

  1. Лексический анализ - разбор на токены
  2. Синтаксический анализ - построение AST (Abstract Syntax Tree)
  3. Семантический анализ - проверка типов
  4. Промежуточное представление (IR)
  5. Оптимизации - если компилирование с -O2, -O3
  6. Генерация ассемблера
; main.s (результат компиляции)
.globl main
main:
    push rbp
    mov rsp, rbp
    mov eax, 0
    pop rbp
    ret

Этап 3: Ассемблирование (Assembly)

Ассемблер (as) преобразует ассемблер в объектный файл:

as main.s -o main.o  # Или gcc -c main.cpp создаёт main.o

Объектный файл (main.o) содержит:

  • Машинный код (инструкции процессора)
  • Таблица символов (функции, переменные)
  • Информация о релокациях (какие адреса нужно исправить при линковке)
  • Debug информация (если компилировать с -g)

Этап 4: Линковка (Linking)

Линкер (ld, gcc) объединяет объектные файлы и библиотеки в исполняемый файл:

ld main.o /usr/lib/crt1.o -lc -o main  # Низкоуровневая линковка
# ИЛИ
g++ main.o -o main  # GCC автоматически вызывает линкер

Процесс линковки:

  1. Находит все глобальные символы (функции, переменные)
  2. Разрешает ссылки между файлами - если main.o вызывает printf, находит её в libc
  3. Назначает финальные адреса в памяти
  4. Создаёт исполняемый файл

Полный процесс: пример

// helper.cpp
int add(int a, int b) {
    return a + b;
}

// main.cpp
#include <iostream>
int add(int a, int b);  // Объявление (declaration)

int main() {
    int result = add(5, 3);
    std::cout << result << std::endl;
    return 0;
}

Полная сборка:

# Этап 1: Препроцессинг (неявно)
# g++ раскрывает #include, обрабатывает макросы

# Этап 2: Компиляция
g++ -c helper.cpp -o helper.o    # Компилируем helper
g++ -c main.cpp -o main.o        # Компилируем main

# Результат: helper.o и main.o содержат машинный код
# helper.o имеет символ "add" (экспортирует функцию)
# main.o имеет ссылку на "add" (нужна функция)

# Этап 3: Линковка
g++ helper.o main.o -o program   # Линкуем объектные файлы

# Результат: исполняемый файл program

Что такое Object File (.o)

Объектный файл (ELF формат на Linux):
┌─────────────────────┐
│ ELF Header          │ Метаинформация
├─────────────────────┤
│ .text section       │ Машинный код
├─────────────────────┤
│ .data section       │ Инициализированные данные
├─────────────────────┤
│ .bss section        │ Неинициализированные данные
├─────────────────────┤
│ .symtab             │ Таблица символов
├─────────────────────┤
│ .rel.text           │ Релокации для кода
├─────────────────────┤
│ .strtab             │ Таблица строк символов
└─────────────────────┘

Undefined reference error

// main.cpp
int compute();  // Объявление

int main() {
    return compute();  // Вызов
}

// Забыли определение!
// compute() не определён нигде
g++ -c main.cpp  # OK, компиляция успешна
g++ main.o -o main
# Ошибка: undefined reference to `compute'

Линкер не может найти символ compute в объектных файлах.

Решение: добавить файл с определением

// helper.cpp
int compute() {
    return 42;
}
g++ -c main.cpp -o main.o
g++ -c helper.cpp -o helper.o
g++ main.o helper.o -o main  # OK!

Static vs Dynamic linking

# Статическая линковка (статические библиотеки .a)
g++ main.o -L/path -lmylib -static
# Вся библиотека встраивается в исполняемый файл
# Размер большой, но нет зависимостей на runtime

# Динамическая линковка (динамические библиотеки .so)
g++ main.o -L/path -lmylib
# Исполняемый файл маленький
# При запуске нужна library в системе

Make и CMake

Для больших проектов нужна система сборки:

# Makefile
CXX = g++
CXXFLAGS = -O2 -std=c++17
TARGET = program
SOURCES = main.cpp helper.cpp
OBJECTS = $(SOURCES:.cpp=.o)

$(TARGET): $(OBJECTS)
	$(CXX) $(OBJECTS) -o $(TARGET)

%.o: %.cpp
	$(CXX) $(CXXFLAGS) -c $< -o $@

clean:
	rm -f $(OBJECTS) $(TARGET)

Ор CMake (более современно):

cmake_minimum_required(VERSION 3.10)
project(MyProject)

add_executable(program main.cpp helper.cpp)
target_compile_options(program PRIVATE -O2 -std=c++17)

Оптимизация во время компиляции

g++ -O0 program.cpp  # Без оптимизации (быстрая компиляция)
g++ -O1 program.cpp  # Небольшие оптимизации
g++ -O2 program.cpp  # Баланс скорость/размер
g++ -O3 program.cpp  # Максимальная оптимизация

Оптимизации:

  • Удаление неиспользуемого кода (dead code elimination)
  • Inlining функций
  • Loop unrolling
  • Vectorization (использование SIMD)
  • Constant folding

Link-time optimization (LTO)

g++ -flto -O3 main.cpp helper.cpp -o program

Линкер может видеть весь код и оптимизировать глобально:

  • Inline через границы файлов
  • Удаление неиспользуемых функций
  • Специализация шаблонов

Ускорение сборки

# Параллельная компиляция (использует все ядра)
make -j$(nproc)  # Компилирует в параллель

# Ccache (кэширование объектных файлов)
ccache g++ main.cpp

# Incremental build (пересобирает только изменённые файлы)
make  # только изменённое

Итог

Препроцессинг: раскрытие #include и макросов ✅ Компиляция: исходный код → ассемблер ✅ Ассемблирование: ассемблер → объектные файлы (.o) ✅ Линковка: объектные файлы + библиотеки → исполняемый файл ✅ Оптимизация: флаги -O2, -O3 ускоряют код ✅ Система сборки: Make, CMake, Bazel управляют процессом ⚠️ Undefined reference: если символ не найден при линковке