Зачем мы компилируем наш код?
Процесс c-компиляции — это способ преобразования нашего исходного кода в машинный код, который компьютер воспринимает как входные данные. Как мы знаем, единственный способ связи с компьютером — это двоичная система (нули и единицы), которые представляют два состояния электричества: включено (1) и выключено (0).
Чтобы перейти к двоичной системе, которую понимает наш компьютер, используется компилятор gcc, происходящий от аббревиатуры Коллекция компиляторов GNU. Это оптимизирующий компилятор, созданный проектом GNU, поддерживающим различные языки программирования, аппаратные архитектуры и операционные системы
Как компилятор gcc преобразует мой код в машинный код?
Чтобы добраться до машинного кода, наш код проходит 4 шага, которые будут кратко объяснены, а затем приведен пример того, как наш код трансформируется на каждом этапе компиляции, чтобы полностью стать кодом, понятным только компьютерам.
Каковы этапы компиляции?
Шагов компиляции 4:
- Препроцессор
- Компилятор
- Ассемблер
- Линкер
Теперь будет подробно из чего состоит каждый шаг компиляции:
1. Препроцессор
Первое, что делает процесс компиляции c, — это проходит через препроцессор. Исходный код написан в текстовом редакторе, и этот файл имеет расширение «.c».
На этапе препроцессора выполняется множество шагов, среди которых наиболее важными являются получение исходного кода в качестве входных данных, удаление комментариев, которые были размещены в коде.
Разверните найденные макросы и разверните включенные файлы. Например, если в программе имеется директива ‹stdio.h›, то препроцессор интерпретирует директиву и заменяет ее содержимым stdio.h.
То, что мы получаем после прохождения препроцессора, представляет собой расширенный код, чтобы остановить процесс после шага препроцессора, мы используем команду gcc -E main.c.
Принимая во внимание, что main.c — это имя файла, который мы компилируем, а его содержимое следующее:
Мы можем с флагом -o назначить имя, которое мы хотим дать результату применения указанной команды, в этом случае мы ставим имя c. Мы можем видеть результат, сгенерированный после первого шага предварительной обработки, и то, как исходный код меняется на то, что мы видим на следующем изображении:
$gcc -E main.c -o c
2. Компилятор
На этом этапе код, расширенный препроцессором, преобразуется компилятором в ассемблерный код. Точно так же, чтобы визуализировать изменение нашего исходного кода с помощью команды -S, мы можем остановить процесс компиляции после прохождения через компилятор и до достижения ассемблера.
Если вы помните, ранее мы поставили флаг -o, чтобы сгенерировать, что результат, который генерируется после примененной команды, имеет имя, которое мы назначаем, но если мы не делаем этого по умолчанию на этом шаге файл с тем же именем исходного кода файл создается, но с расширением .s, как мы видим ниже:
$gcc -S main.c
3. Ассемблер
На этом этапе ассемблер преобразует ассемблерный код в объектный код. Чтобы иметь возможность увидеть результат нашего исходного кода после прохождения ассемблера, мы используем команду -c, и это по умолчанию создает файл main.o, который при отображении с помощью команды cat мы можем увидеть его содержимое, как показано на следующее изображение.
$gcc main.c -c
4. Линкер
На этом последнем шаге вызовы функций связываются с их определениями. Кроме того, важно отметить, что компоновщик добавляет в нашу программу дополнительный код, который требуется при запуске и завершении программы.
Чтобы проверить этот последний шаг, мы можем использовать команду $size main.o и $size main, последняя является конечным результатом всего процесса компиляции, в котором она становится исполняемым файлом.
Таким образом, с этими последними командами мы можем узнать, как выходной файл увеличивается от объектного файла или объектного кода до исполняемого файла, и это связано с дополнительным кодом, который компоновщик добавляет в нашу программу, и это можно увидеть в текстовый столбец, в котором видно, что он значительно увеличивается от одного шага к другому.
Таким образом, мы смогли увидеть пошаговый процесс, через который проходит наш исходный код, когда мы выполняем процесс компиляции на C.
Благодаря этому мы можем общаться с нашим компьютером без необходимости писать весь наш код в двоичном виде, что было бы действительно ужасной идеей.
Спасибо за внимание.