C является скомпилированным языком, что означает, что для выполнения кода его необходимо скомпилировать, но переход от удобочитаемого кода к машинному коду — это не один шаг, а несколько шагов, каждый из которых играет важную роль в процессе.
1. Предварительная обработка
Это первый шаг, который делает компилятор, и он в основном состоит из шагов по очистке кода для чтения остальной части:
- Удаление комментариев.
- Расширение макросов.
- Расширение файлов заголовков, как при копировании содержимого файлов заголовков и копировании его в исходный файл.
Например, у нас есть файл main.c:
Мы можем предварительно обработать код с помощью gcc -E main.c
.
2. Компиляция
Он состоит из чтения предварительно обработанного кода и генерации из него ассемблерного кода.
Снова с main.c мы можем сделать gcc -S
, чтобы скомпилировать наш код в сборку:
3. Ассемблер
Ассемблер преобразует ассемблерный код в машинный код в файл, называемый объектным файлом, который обычно заканчивается расширением .o
так же, как и другие, которые мы можем получить, достигнув этого шага с помощью gcc -c main.c
:
4. Связывание
Это последний шаг в процессе компиляции. Компоновщик объединяет весь объектный код из нескольких модулей в один. Если мы используем функцию из библиотек, компоновщик свяжет наш код с кодом этой библиотечной функции.
При статической компоновке компоновщик копирует все используемые библиотечные функции в исполняемый файл. При динамической компоновке код не копируется, это делается путем простого помещения имени библиотеки в исполняемый файл.
Как видите, компиляция не такая, как кажется, и это не просто один простой шаг. Хотя это только поверхностное прикосновение, так как под каждым из этих шагов лежат другие важные дела, которые делают компилятор тем, чем он является.