Коллекция компиляторов GNU (GCC) — это система компиляторов, разработанная проектом GNU, которая поддерживает различные языки программирования. GCC — это стандартный компилятор для большинства Unix-подобных операционных систем, а также ключевой компонент цепочки инструментов GNU. В этой статье я объясню, что именно происходит на каждом этапе процесса компиляции. В общих чертах последовательность событий после того, как команда компиляции передана GCC, включает:

  • Предварительная обработка
  • Сборник
  • сборка
  • Связывание

Привет мир!

В качестве примера мы рассмотрим каждый этап компиляции, используя всеми любимую программу «Hello World» main.c . Код для main.c выглядит следующим образом:

#include <stdio.h>
int main (void)
{
  printf ("Hello, world!\n");
  return 0;
}

Важно отметить, что GCC выполняет каждую последовательность команд автоматически, не сообщая о том, что именно происходит за кулисами. Однако, если вы хотите увидеть выполнение каждого отдельного шага, gcc -v main.c запустит подробную компиляцию, которая отобразит подробную информацию о выполняемых командах. Первая строка программы, #include<stdio.h>, представляет собой заголовочный файл, определяющий процедуры ввода/вывода, используемые нашей программой. Следующая строка кода, int main (void), является «точкой входа» в нашу программу. За ним следует printf, который выводит символы между кавычками в «стандартный вывод». Символ \n не печатается, потому что gcc интерпретирует его как «новую строку». Наконец, программа возвращает целое число 0 в последней строке кода, поскольку мы определили основную процедуру как возвращающую целое число (int ).

Препроцессор

Когда мы пишем программу на C, первым этапом процесса компиляции является развертывание макросов и включенных заголовочных файлов через препроцессор. Для этого GCC выполняет следующую команду:

$ cpp main.c > main.i

Полученный файл main.i содержит исходный код с расширенными макросами. В соответствии с соглашением программирования C предварительно обработанные файлы помечаются расширением '.i'.

Компилятор

На этом этапе происходит компиляция предварительно обработанного исходного кода на ассемблере для конкретного процессора. В командной строке, чтобы преобразовать предварительно обработанный исходный код C в язык ассемблера, введите следующий код и нажмите Enter:

$ gcc -Wall -S main.i

Обратите внимание, что запущенный gcc с параметром -S не создаст объектный файл. В результате получается файл main.s. Чтобы увидеть язык ассемблера, хранящийся в файле, введите в командной строке следующее:

$ cat main.s

Ассемблер

Как следует из названия, ассемблер преобразует язык ассемблера в машинный код и генерирует объектный файл. Когда в исходном файле сборки выполняются вызовы внешних функций, ассемблер оставляет адреса внешних функций неопределенными, чтобы компоновщик мог заполнить их на следующем шаге процесса. Чтобы сгенерировать объектный файл с помощью ассемблера, введите и выполните эту команду:

$ as main.s -o main.o

Параметр -o выше указывает, что вывод должен быть направлен в файл main.o. Файл main.o содержит инструкции на языке, понятном машине, для запуска нашей программы. Как я упоминал ранее, в этот момент любые вызовы внешних функций будут неопределенными.

Линкер

На этом этапе процесса компиляции компоновщик приступает к связыванию объектных файлов для создания исполняемого файла. Создание исполняемого файла обычно требует вызовов внешних функций из библиотек времени выполнения C и системы. Например, полная команда для связывания нашей программы:

$ ld -dynamic-linker /lib/ld-linux.so.2 /usr/lib/crt1.o 
 /usr/lib/crti.o /usr/lib/gcc-lib/i686/3.3.1/crtbegin.o 
 -L/usr/lib/gcc-lib/i686/3.3.1 main.o -lgcc -lgcc_eh
 -lc -lgcc -lgcc_eh /usr/lib/gcc-lib/i686/3.3.1/crtend.o 
 /usr/lib/crtn.o

К счастью, нам никогда не потребуется напрямую вводить эту гигантскую команду, потому что процесс связывания незаметно обрабатывается gcc при вводе следующего кода:

$ gcc main.o

Эта команда связывает объектный файл main.o со стандартной библиотекой C, после чего создается исполняемый файл с именем a.out. Чтобы запустить программу Hello World, просто введите:

$ ./a.out

Вывод должен выглядеть так, если все сделано правильно:

$ ./a.out
Hello, world!

Теперь, когда мы понимаем, сколько всего требуется на каждом этапе процесса компиляции, мы можем по-настоящему оценить простоту ввода gcc main.c в терминал и предоставления GCC выполнения тяжелой работы.

Источник: http://www.network-theory.co.uk/docs/gccintro/gccintro_87.html