В начало → Изучаем параметры gcc → Опции, управляющие стадиями компиляции. |
В целях изучения, иногда вы хотите узнать, как ваш исходный код трансформируется в исполняемый. К счастью, gcc предоставляет вам опции для остановки на любой стадии обработки. Вспомните, что gcc имеет несколько стадий завершения, — например, компоновку. Есть такие опции:
-c останавливает на стадии ассемблирования, но пропускает компоновку. Результатом является объектный код.
-E останавливает на стадии препроцессинга. Все директивы препроцессора развернуты, так что вы видите только чистый код.
-S останавливает после компиляции. Она оставляет вас с ассемблерным кодом.
c наиболее часто используется, когда у вас есть несколько исходных файлов и вы хотите скомбинировать их для получения итогового исполняемого файла. Так что, вместо такого:
$
gcc -o final-binary test1.c test2.c
будет лучше разделить их так:
$
gcc -c -o test1.o test1.c
$
gcc -c -o test2.o test2.c
и затем:
$
gcc -o final-binary ./test1.o ./test1.o
Возможно, вы заметили, что такая же последовательность используется, если вы собираете программу, используя Makefile. Преимущество использования -c ясно: вам нужно перекомпилировать только измененные исходные файлы. Только фаза, на которой переделывается компоновка всех объектных файлов, и это очень экономит время, особенно в больших проектах. Очевидным примером этого является ядро Linux.
E будет полезна, если вы хотите посмотреть, как ваш код в действительности выглядит после разворачивания макросов, определений и т.п. Возьмите следующий листинг в качестве примера:
#include #define A 2 #define B 4 #define calculate(a,b) a*a + b*b void plain_dummy() { printf("Just a dummy\n"); } static inline justtest() { printf("Hi!\n"); } int main(int argc, char *argv[]) { #ifdef TEST justtest(); #endif printf("%d\n", calculate(A,B)); return 0; }
Скомпилируем его следующим образом:
$
gcc -E -o listing2.e listing2.c
Учтите, что мы не указываем параметров -D, что означает, что TEST не определен. Так и что мы имеем в препроцессорном файле?
void plain_dummy() { printf("Just a dummy\n"); } static inline justtest() { printf("Hi!\n"); } int main(int argc, char *argv[]) { printf("%d\n", 2*2 + 4*4); return 0; }
А где вызов justtest() внутри main()? Нигде. TEST не определен — вот почему код исключен. Вы также можете видеть, что макрос calculate уже развернут в умножение и сложение констант. В конечной исполняемой форме, эти числа будут заменены результатами операций. Как вы видите, -E довольно удобна для проверки корректности директив.
Обратите внимание, что plain_dummy() все ещё здесь, не смотря на то, что она ни разу не вызывается. Это не удивительно, так как ещё не была произведена компиляция, вот почему не произошло исключение «мертвого» кода на этой стадии. stdio.h также развернут, но не показан в листинге выше.
Я нашел интересное приложение использования -E в качестве утилиты создания HTML. Вкратце, она помогает вам перенимать обычные действия в программировании, такие как модуляризация кода и макросы в мир HTML — то, что не может быть сделано на чистом HTML.-S дает вам код на ассемблере, больше похожий на то, что вы видите с помощью objdump -d/-D. Хотя с помощью -S вы продолжите видеть директивы и имена символов, который делают код проще к изучению. Например, вызов printf("%d\n", 20) может быть трансформирован в:
.section .rodata.str1.1,"aMS",@progbits,1 .LC0: .string "%d\n" ... movl $20, 4(%esp) movl $.LC0, (%esp) call printf
Вы можете видеть, что format string %d помещена в область данных, доступную только для чтения (.rodata). Также, вы можете удостовериться, что аргументы помещаются в стек справа налево, со строкой форматирования на верху стека.
В начало → Изучаем параметры gcc → Опции, управляющие стадиями компиляции. |