Как получить ассемблерный листинг из C/C++ кода в GCC?
Основной способ: флаг -S
Самый простой способ — использовать флаг -S. Он заставляет компилятор остановиться после стадии генерации ассемблера и выдать файл с расширением .s. Однако, как вы заметили, по умолчанию вывод содержит много служебной информации (директивы отладки, CFI, секции и т.д.). Чтобы сделать его чище, добавьте флаг -fno-asynchronous-unwind-tables (убирает таблицы раскрутки стека для исключений) и -fno-dwarf2-cfi-asm (отключает часть CFI-директив). Для полной "чистоты" можно использовать -O0 (без оптимизаций) или -O1/-O2 для оптимизированного кода.
Пример команды:
g++ -S -O2 -fno-asynchronous-unwind-tables -fno-dwarf2-cfi-asm hello.cpp -o hello.s
В файле hello.s останется в основном только код функций и минимальные директивы.
Просмотр в терминале без создания файла
Используйте флаг -c (только компиляция) и передайте вывод в утилиту objdump с флагом -d (дизассемблирование). Это не даст вам "чистый" ассемблер, но покажет машинные инструкции. Для перемешивания с исходным кодом добавьте -S (заглавная S).
g++ -c -O2 hello.cpp -o /dev/stdout | objdump -d -S -
Флаг -S в objdump (заглавная S) попытается вставить строки исходного кода, если они есть в отладочной информации. Для этого нужно компилировать с -g:
g++ -c -g -O2 hello.cpp -o /dev/stdout | objdump -d -S -
Лучший способ: использовать GCC с флагом -masm=intel и -fverbose-asm
Для максимально читаемого вывода рекомендую:
-masm=intel— выводить ассемблер в синтаксисе Intel (более привычный для многих, чем AT&T).-fverbose-asm— добавлять комментарии с именами переменных и выражений из исходного кода.-O0или-O1— для простоты чтения (без сильных оптимизаций).-fno-asynchronous-unwind-tables— убрать лишние директивы.
Пример для вашего hello.cpp:
g++ -S -O0 -masm=intel -fverbose-asm -fno-asynchronous-unwind-tables hello.cpp
В файле hello.s вы увидите примерно такой код (упрощённо):
main:
push rbp
mov rbp, rsp
; std::cout << "Hello\n"
mov esi, OFFSET FLAT:.LC0 ; "Hello"
mov edi, OFFSET FLAT:std::cout
call std::basic_ostream<char, std::char_traits<char>>::operator<<(char const*)
mov eax, 0
pop rbp
ret
.LC0:
.string "Hello"
Онлайн-инструменты
Если не хотите возиться с командной строкой, используйте Compiler Explorer (godbolt.org). Там можно выбрать компилятор, флаги, и сразу видеть ассемблерный код с подсветкой соответствия строк C++.
Резюме
- Для файла:
g++ -S -O2 -masm=intel -fverbose-asm -fno-asynchronous-unwind-tables source.cpp - Для терминала с исходным кодом:
g++ -c -g -O2 source.cpp -o /dev/stdout | objdump -d -S - - Для экспериментов: Compiler Explorer
Важно: при высоких уровнях оптимизации (-O2, -O3) код может быть сильно переписан (циклы развёрнуты, функции встроены), и соответствие исходному коду будет менее очевидным. Для обучения лучше начинать с -O0.
Альтернативный подход: использование GDB
Если вам нужно не просто увидеть ассемблер, а пошагово проследить выполнение программы и посмотреть, какие инструкции генерируются для конкретных строк кода, используйте отладчик GDB в режиме дизассемблера.
Скомпилируйте программу с отладочной информацией (-g) и без оптимизаций (-O0), затем запустите GDB:
g++ -g -O0 hello.cpp -o hello
gdb ./hello
(gdb) break main
(gdb) run
(gdb) display /i $pc # показывать текущую инструкцию при каждом шаге
(gdb) stepi # шаг на одну машинную инструкцию
(gdb) layout asm # переключиться в режим просмотра ассемблера
(gdb) layout split # разделить экран: исходный код + ассемблер
Команда layout split особенно полезна — вы видите одновременно исходный код на C++ и соответствующий ему ассемблер. Перемещаться между инструкциями можно клавишами Up/Down, а выполнять шаги — командой stepi.
Этот метод удобен для глубокого понимания, как работают вызовы функций, передача аргументов, работа со стеком и регистрами. Он также позволяет увидеть влияние оптимизаций в реальном времени, если перекомпилировать с -O2.