Все статьи Исследуем работу компилятора C/C++
Есть всего три популярных, высококачественных, широко принятых в индустрии компиляторов C/C++:
- GCC (Gnu Compiler Collections и GNU C Compiler), кроссплатформенный и Open-Source, используется в Linux как основной, на Windows известен как MinGW
- MSVC (Microsoft Visual C/C++), низкая кроссплатформенность и закрытый код, используется в Windows как основной
- LLVM/Clang, кроссплатформенный и Open-Source, используется в Mac OSX как основной, на Windows умеет быть совместимым и с MinGW, и с MSVC, доступен в Visual Studio 2015 и выше в модификации Clang/C2
Принципы работы GCC и Clang можно детально исследовать благодаря открытому исходному коду и отладочным средствам.
GCC (компиляция и вывод ассемблера)
Разберёмся, как использовать GCC из командной строки. На UNIX-платформах GCC доступен по команде gcc
, а для Windows есть порт GCC — MinGW. Воспользуемся примером кода, складывающего два числа:
#include <stdio.h>
float sum(float a, float b)
{
return a + b;
}
int main()
{
float a = 0;
float b = 0;
scanf("%f %f", &a, &b);
float ab = sum(a, b);
printf("a + b = %f\n", ab);
}
Компиляция файла из командной строки с опциями по умолчанию (отладочная сборка без оптимизаций):
# после флага -o задан выходной путь
# все параметры вне флагов считаются входными путями
gcc a+b.c -o a+b
Вывод программы после запуска:
10 29
a + b = 39.000000
Получение ассемблерного кода для отладочного режима без оптимизаций возможно с опцией -S
. По умолчанию создаваемый ассемблер использует синтаксис AT&T, который заметно отличается от синтаксиса Intel.
# -oa+b_debug.s необязательная опция, указывает явно имя выходного файла
# -S указывает генерировать ассемблер вместо исполяемого кода
gcc -S a+b.c -oa+b_debug.s
# -masm=intel указывает на смену синтаксиса выходного ассемблера
gcc -S a+b.c -oa+b_debug.s -masm=intel
Можно получить ассемблерный код в режиме с оптимизациями, используя флаг -O2
, где “O” в верхнем регистре. Если сравнить отладочный и оптимизированный код с помощью утилиты diff, будут видны сильные отличия в цепочках инструкций.
# -oa+b_debug.s необязательная опция, указывает явно имя выходного файла
# -S указывает генерировать ассемблер вместо исполяемого кода
# -O2 указывает второй уровень оптимизаций, аналогичный Release-сборкам
gcc -O2 -S a+b.c -oa+b_debug.s
Вы можете скомпилировать ассемблер с помощью того же gcc, который сам передаст нужные параметры утилите “gas” (GNU Assembler).
gcc a+b_debug.s
Clang (компиляция, вывод ассемблера и LLVM-IR)
Clang разрабатывался как прозрачная замена компилятору GCC для Linux и Mac OSX. Поэтому большая часть опций, касающихся компиляции C/C++, у этих двух компиляторов совпадает. Компиляция примера на языке C выглядит точно так же:
# после флага -o задан выходной путь
# все параметры вне флагов считаются входными путями
clang a+b.c -o a+b
Генерация ассемблера с синтаксисом Intel:
clang -S -mllvm --x86-asm-syntax=intel a+b.c
Бекенды GCC и Clang
GCC и Clang оба используют гибкие фреймворки для построения бекендов компилятора. В GNU Compiler Collections используется собственный промежуточный язык и бекенд GIMPLE, который сильно упрощает написание компиляторов для новых языков в составе GNU Compiler Collections, но плохо подходит для изучения новичком. Проект LLVM гораздо дружественнее к новичкам и студентам, и именно его использует компилятор Clang.
Вы можете изучать промежуточный код проекта LLVM, называемый LLVM-IR, с помощью clang, исследуя преобразование кода из C в LLVM-IR:
# Выходной файл: a+b.ll
clang -S -emit-llvm a+b.c
# Компиляция с оптимизациями (O2)
# Выходной файл: a+b.ll
clang -O2 -S -emit-llvm a+b.c
Упражнения
- Напишите 3-4 простейших программы в 10-20 строк на C (сложение двух чисел, вывод текущего времени с начала эпохи UNIX, вывод версии операционной системы, переворачивание строки т.п.). Сгенерируйте из этих программ листинги в машинном ассемблере либо в LLVM-IR, и сравните листинги от разных программ с помощью diff. Попробуйте собрать минимальный шаблон ассемблерного кода, который можно было бы разворачивать в полноценную программу путём подстановки цепочки инструкций вместо переменной
{CODE}
.