Что происходит, когда вы вводите gcc main.c

Когда большинство людей думают о слове «сборник», они, вероятно, думают о своем любимом альбоме лучших хитов. Хотя Bob Marley Legend — фантастический сборник компиляций, мы займемся другим видом компиляции: преобразованием исходного кода из языка программирования C в двоичный исполняемый файл на вашем компьютере. И в самом прямом смысле это представляет собой один из величайших хитов информатики.

Язык C разрабатывался Деннисом Ритчи с 1969 по 1973 год, когда он работал в Bell Labs, где он использовался для перезаписи более ранней операционной системы Multics в более переносимую и компактную форму, что в конечном итоге привело к в ОС Unix. До появления языка C наиболее важный код, такой как ядра ОС и другое системное программное обеспечение, был написан на языке ассемблера, который был очень специфичен для конкретной архитектуры, для которой он был разработан. Отчасти C был задуман как ответ на проблемы с переносимостью, возникающие при попытке перенести программное обеспечение, написанное для одной архитектуры, в другую. Ключевым аспектом успеха C была возможность писать код на более высоком уровне абстракции и компилировать его на языке ассемблера машинно-зависимым образом.

Вместо того, чтобы писать непосредственно на ассемблере, который трудно читать и более подвержен человеческим ошибкам, компиляция языка более высокого уровня, такого как C, в ассемблере позволяет программистам писать более сложный код, который может быть выполнен в любой системе с помощью компилятора, способного создание сборки для него. Один из популярных наборов компиляторов, GCC, разработан GNU Software Foundation. GCC был реализован для многих различных архитектур и позволяет программистам на C и C++ писать общие программы, а затем компилировать их для самых разных архитектур без необходимости становиться экспертами в каждой среде развертывания.

Язык ассемблера также транслируется компилятором в объектный код, который содержит инструкции (в двоичном виде), которые непосредственно интерпретируются процессором.

Этапы компиляции

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

Исходный код содержит разнообразную информацию, часть которой, например комментарии, бесполезна для компилятора (но очень полезна для человека). Когда вы вводите gcc main.c, первое, что происходит, это то, что исходный код передается cpp, процессору C prep. Эта программа удаляет комментарии и интерпретирует директивы и макросы, используемые cpp для изменения и подготовки кода различными способами. Одной из наиболее важных функций является интерпретация директив #include, которые определяют файлы заголовков для копирования и вставки препроцессором непосредственно в исходный файл. Например, чтобы использовать printf, необходимо включить заголовочный файл stdio.h, который объявляет printf и другие необходимые функции для компилятора.

Несмотря на то, что gcc обычно называют компилятором, он больше похож на проводник, который переносит исходный файл через процесс компиляции, гарантируя, что каждый шаг получает правильные входные данные и параметры, а также отлавливает ошибки. Используя gcc, можно остановить компиляцию в любой момент процесса. Например, чтобы увидеть вывод cpp, вы можете запустить gcc -E main.c, который напечатает в stdout исходный код без комментариев и содержащий все объявления из stdio.h.

После предварительной обработки исходный код передается программе-компилятору. cc1 — это компилятор, который gcc использует для преобразования исходного кода в язык ассемблера. Многие параметры, доступные gcc, передаются cc1, чтобы повлиять на то, как он преобразует исходный код, например, для каких архитектур ЦП и операционных систем. Опять же, можно просмотреть результат этого шага, передав флаг -S в gcc:

По умолчанию gcc -S выводит файл так же, как источник, но с измененным расширением на .s. Преимущество синтаксиса C легко увидеть при непосредственном сравнении его с выводом на ассемблере.

После того, как cc1 преобразует исходный код в язык ассемблера, ассемблер as должен преобразовать его в объектный код, из которого можно построить двоичный исполняемый файл. Как и в случае с cpp и cc1, можно захватить вывод as, используя флаг -c для gcc:

Поскольку объектный код является двоичным и использует набор инструкций для связи с ЦП, очень небольшая его часть отображается в виде разборчивого текста. Вывод as имеет расширение .o, чтобы указать, что это объектный код.

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

Последним шагом является объединение объектных файлов в единый монолитный двоичный файл, содержащий все инструкции, необходимые для запуска программы. Это достигается с помощью ld, программы компоновщика. В дополнение к объектным файлам, созданным из исходного кода, ld часто извлекает дополнительный предварительно скомпилированный код из различных библиотечных функций. Например, функция printf, объявленная stdio.h в исходном файле, на этом шаге будет связана с остальным исполняемым файлом. Связывая все эти различные файлы вместе в правильном порядке, создается действительный исполняемый файл, который может работать на целевой архитектуре.

И последнее замечание: после того, как вы сгенерируете свой исполняемый файл с помощью gcc main.c, вы можете задаться вопросом, где он находится? По умолчанию gcc вызывает вашу программу a.out и помещает ее в ту же папку, что и исходный файл. Вы можете указать имя вывода, используя gcc SOURCE_FILE -o OUTPUT_NAME. Это особенно важно, когда вы работаете с несколькими исполняемыми файлами в одном каталоге, потому что, если вы не укажете уникальное выходное имя, ваш исполняемый файл будет перезаписан любыми дополнительными компиляциями.