Процесс компиляции включает четыре последовательных этапа: предварительная обработка, компиляция, сборка и связывание. Чтобы перейти от написанной человеком исходной программы к исполняемому файлу, необходимо последовательно выполнить эти четыре этапа. Команды gcc и g++ могут выполнять весь процесс сразу, в этом мы будем использовать gcc, и четыре этапа:
Предварительно обработано:
На этом этапе директивы интерпретируются препроцессору. Среди прочего, переменные, инициализированные с помощью #define, заменяются в коде их значением во всех местах, где встречается их имя.
В качестве примера мы будем использовать эту простую программу:
#define PI 3.1416 main() { float area, radio; radius 10; area - PI * (radio * radio); printf("Circle."); printf("%s%f-n-n", "Circle area radius 10: ", area); }
Предварительную обработку можно заказать с помощью любой из следующих команд; cpp конкретно относится к препроцессору.
$ gcc -E circle.c > circle.pp $ cpp circle.c > circle.pp
Если мы изучим круг.pp, то увидим, что переменная PI была заменена ее значением 3,1416, как указано в решении #define.
Компиляция:
Компиляция преобразует код C в язык ассемблера процессора нашей машины.
$ gcc -S circle.c
Сборка:
Что делает сборка, так это преобразует программу, написанную на языке ассемблера, в объектный код, двоичный файл на исполняемом машинном языке процессором.
Ассемблер называется так:
$ as -o circle.o circle.s
создает файл в объектном коде circle.o из ассемблерного файла circle.s. Не принято выполнять только сборку; обычно выполняются все вышеперечисленные этапы до тех пор, пока вы не получите такой объектный код:
$ gcc -c circulo.c
Это создает файл circle.o из circle.c.
В больших программах, где многие исходные файлы написаны в коде C, очень часто используется gcc или g++ с параметром -c для компиляции каждого исходного файла отдельно. , а затем привязать все созданные объектные модули. Эти операции автоматизированы путем помещения их в файл с именем make file, интерпретируемый командой make, которая заботится о необходимых минимальных обновлениях всякий раз, когда какая-либо часть кода изменяется в любом из исходных файлов.
4. Linked
Последний этап — собрать один или несколько модулей в объектном коде с кодом, существующим в библиотеках.
Для привязки используем команду ld, для получения исполняемого файла используем что-то вроде этого:
$ ld -o cirle /usr/lib/gcc-lib/i386-linux/2.95.2/collect2 -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o circle /usr/lib/crt1.o /usr/lib/crti.o /usr/lib/gcc-lib/i386-linux/ 2.95.2/crtbegin.o -L/usr/lib/gcc-lib/i386-linux/2.95.2 circle.o -lgcc -lc -lgcc /usr/lib/gcc-lib/i386-linux/2.95.2/crtend.o /usr/lib/crtn.o
Как сделать все за один шаг?
В программе с одним исходным файлом весь описанный выше процесс можно выполнить за один шаг:
$ gcc -o circle circle.c
Файл circle.o не создается; промежуточный объектный код создается и уничтожается незаметно для оператора, но исполняемая программа появляется там и работает.
Поучительно использовать опцию -v gcc для получения подробного отчета обо всех шагах сборки:
$ gcc -v -o circle.c