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

1. Предварительная обработка

Это первый шаг, который делает компилятор, и он в основном состоит из шагов по очистке кода для чтения остальной части:

  1. Удаление комментариев.
  2. Расширение макросов.
  3. Расширение файлов заголовков, как при копировании содержимого файлов заголовков и копировании его в исходный файл.

Например, у нас есть файл main.c:

Мы можем предварительно обработать код с помощью gcc -E main.c .

2. Компиляция

Он состоит из чтения предварительно обработанного кода и генерации из него ассемблерного кода.

Снова с main.c мы можем сделать gcc -S , чтобы скомпилировать наш код в сборку:

3. Ассемблер

Ассемблер преобразует ассемблерный код в машинный код в файл, называемый объектным файлом, который обычно заканчивается расширением .o так же, как и другие, которые мы можем получить, достигнув этого шага с помощью gcc -c main.c :

4. Связывание

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

При статической компоновке компоновщик копирует все используемые библиотечные функции в исполняемый файл. При динамической компоновке код не копируется, это делается путем простого помещения имени библиотеки в исполняемый файл.

Как видите, компиляция не такая, как кажется, и это не просто один простой шаг. Хотя это только поверхностное прикосновение, так как под каждым из этих шагов лежат другие важные дела, которые делают компилятор тем, чем он является.