Новичку команда gcc
кажется магией. В одном конце идет файл с каким-то кодом на C, а в другом выплевывается исполняемый файл. Лучший способ демистифицировать это — изучить четыре этапа компиляции: предварительная обработка, компиляция, сборка и компоновка.
При предварительной обработке компилятор переводит директивы препроцессора в объекты, которые они представляют. Директивы препроцессора — это строки в верхней части файлов, которые начинаются с #
. Например, предположим, что у нас есть следующий файл:
#include <stdio.h>
включает код из заголовочного файла stdio.h
в исходный код этой программы. #define EXAMPLE “example\n”
заменяет токен EXAMPLE
, встречающийся в любом месте кода, на example\n
. Мы можем увидеть это, если остановим процесс компиляции после предварительной обработки, используя опцию -E
. Заголовочные файлы обычно содержат такие директивы.
При компиляции компилятор переводит высокоуровневый код (в данном случае написанный нами файл .c
) в низкоуровневый ассемблерный код. Это необходимо, потому что нецелесообразно переводить напрямую с языков высокого уровня в машинный код. Отдельный фрагмент высокоуровневого кода может выполняться на любом компьютере, но каждый тип компьютеров имеет для этого различную архитектуру. К тому времени, когда один и тот же фрагмент высокоуровневого кода фильтруется в машинный код с помощью разных компиляторов, он будет выглядеть совсем по-другому.
Ассемблерный код читается людьми, но писать его было бы неинтересно. Мы можем увидеть ассемблерный код, если остановим процесс компиляции после компиляции, используя опцию -S
.
При ассемблере компилятор переводит ассемблерный код в машинный код. Машинный код выполняется непосредственно процессором. Чтобы остановить компиляцию после сборки, используйте опцию -c
. Мы можем увидеть машинный код в двоичном виде, используя команду od
с опцией -c
.
При компоновке компилятор берет файл или файлы машинного кода и упорядочивает их в исполняемый файл. Чтобы увидеть результат этого, мы можем просто запустить gcc
без каких-либо параметров, поскольку связывание — это последняя часть процесса. Имя файла по умолчанию для исполняемого файла — a.out, но можно использовать параметр -o
, чтобы дать ему другое имя. Чтобы увидеть результат компиляции, введите ./
, а затем имя файла.
Это обзор компиляции, поэтому многие нюансы выходят за рамки данной статьи. Для некоторых связанных концепций взгляните на эти страницы Википедии:
Удачного кодирования!