Перейти к содержанию

Стадии сборки программы

Сборка программы на C или C++ — это многошаговый процесс преобразования исходного текста в исполняемый файл. Компилятор gcc/g++ выступает драйвером: он последовательно вызывает несколько специализированных инструментов, каждый из которых отвечает за свою стадию.

Обзор стадий

Стадия Вход Выход Инструмент
Препроцессинг .cpp / .c .i cc1plus, cpp
Компиляция .i .s cc1plus
Ассемблирование .s .o as
Линковка .o, .a, .so бинарь / .so ld, collect2
 main.cpp   foo.cpp
    │          │
    │  g++ -E  │   препроцессинг: раскрытие #include, #define
    ▼          ▼
 main.i    foo.i
    │          │
    │  cc1plus │   компиляция: синтаксический разбор, оптимизации, кодогенерация
    ▼          ▼
 main.s    foo.s
    │          │
    │    as    │   ассемблирование: мнемоники → машинные байты
    ▼          ▼
 main.o    foo.o    libc.a / libfoo.so
    │          │          │
    └──────────┴──────────┘
               │    ld   линковка: разрешение символов, релокации
            a.out  (ELF ET_EXEC или ET_DYN)

Флаг -v заставляет g++ напечатать все порождаемые команды:

g++ -v main.cpp

В выводе будет видно, что драйвер вызывает cc1plus, затем as, затем collect2/ld.

Препроцессинг

Препроцессор обрабатывает директивы, начинающиеся с #:

  • #include — вставляет содержимое заголовочных файлов;
  • #define / #undef — определяет и отменяет макросы;
  • #if, #ifdef, #ifndef, #elif, #else, #endif — условная компиляция;
  • #pragma — платформенно-специфичные директивы.

Результат — обычный текстовый файл с расширением .i (или .ii для C++), в котором нет никаких директив: только реальный код с уже подставленными заголовками и развёрнутыми макросами.

Остановиться после препроцессинга:

g++ -E main.cpp -o main.i

Компиляция

Компилятор читает препроцессированный файл и генерирует ассемблерный текст (.s). На этой стадии происходят:

  • лексический и синтаксический разбор;
  • семантический анализ и проверка типов;
  • оптимизации (если включены флагами -O1, -O2, -O3, -Os и т.д.);
  • генерация кода для целевой архитектуры.

Остановиться после компиляции (получить ассемблер):

g++ -S main.cpp -o main.s

Можно передать также -masm=intel, чтобы получить ассемблер в Intel-синтаксисе вместо AT&T.

Ассемблирование

Ассемблер (as) переводит текстовые мнемоники в машинные байты и упаковывает результат в объектный файл формата ELF с типом REL (Relocatable).

g++ -c main.cpp -o main.o
# или из уже готового .s:
as main.s -o main.o

Объектный файл содержит машинный код и данные, но часть символов остаётся неразрешённой — в таблице символов такие записи помечаются как UND. Запустить объектный файл напрямую нельзя: нет точки входа _start, не завершены релокации.

Линковка

Линковщик (ld) объединяет один или несколько объектных файлов и нужные библиотеки, разрешает все внешние ссылки между символами, выполняет релокации и формирует итоговый ELF-файл: исполняемый (EXEC или DYN/PIE) либо разделяемую библиотеку (.so).

g++ main.cpp -o main          # полный цикл: препроцессинг → линковка
g++ main.o other.o -o main    # только линковка уже готовых .o

Объектный файл vs исполняемый файл

Свойство Объектный файл (.o) Исполняемый файл
Тип ELF REL (Relocatable) EXEC или DYN (PIE)
Неразрешённые символы есть (UND) нет (все разрешены или делегированы динамическому линковщику)
Точка входа _start отсутствует присутствует
Можно запустить нет да

Подробнее о структуре ELF-файлов — в статье Формат ELF.

Флаги оптимизации и отладки

Флаг -g добавляет в бинарь отладочную информацию в формате DWARF: соответствие адресов строкам исходника, имена переменных и типы. Он не влияет на логику программы, но увеличивает размер файла.

g++ -g -O0 main.cpp -o main_debug    # отладочная сборка
g++ -O2 main.cpp -o main_release     # релизная сборка
g++ -g -O2 main.cpp -o main          # отладочная информация + оптимизации

Подробнее об использовании отладочной информации — в статье Отладка (GDB).

Дизассемблирование

Дизассемблировать готовый бинарь можно утилитой objdump:

objdump -d a.out              # AT&T-синтаксис
objdump -d -M intel a.out     # Intel-синтаксис
objdump -C -d a.out           # с демангированием C++-имён

Либо через gdb:

gdb ./a.out
(gdb) disassemble main

Создание статической библиотеки

Статическая библиотека (.a) — это ar-архив объектных файлов:

g++ -c foo.cpp -o foo.o
g++ -c bar.cpp -o bar.o
ar rcs libmylib.a foo.o bar.o   # создать архив

Флаги ar: r — вставить/заменить, c — создать архив если не существует, s — обновить индекс символов (аналог ranlib).

Подробнее о статической и динамической линковке — в статье Статическая и динамическая линковка.

Источники

  • man gcc — документация по флагам компилятора
  • man as — документация ассемблера GNU
  • man ld — документация линковщика GNU
  • man ar — создание архивов статических библиотек
  • GCC Internals: Compilation Phases
  • info gcc — полная документация GCC