Процесс компиляции программ на языке C (1110654)
Текст из файла
Процесс Си-компиляции - Обзор
Цель этого приложения — описать фазы компиляции программ на языке C и научить настраивать компилятор под ваши нужды. Компилятор C преобразует исходный текст на языке C в кодах ASCII в выполняемый объектный код. Процесс компиляции разделен на четыре фазы:
. Препроцессор:
- Осуществляет вставку исходных текстов из других файлов (#include)
- Раскрывает макроопределения (#define)
- Осуществляет условную обработку исходного файла (#ifdef)
- Уничтожает комментарии
. Транслятор (компилятор)
- Проверяет текст на отсутствие синтаксических ошибок
- Преобразует конструкции языка C в конструкции ассемблера
- Выполняет машинно-независимые и машинно-зависимые оптимизации
- Генерирует отладочную информацию.
. Ассемблер
- Преобразует конструкции языка ассемблера в машинные команды
- Генерирует объектный модуль и списки экспорта и импорта (списки внешних символов)
- У некоторых компиляторов этот этап исполняется той же командой, что и трансляция
. Редактор связей
- Осуществляет сборку объектных файлов в загружаемый модуль
- Просматривает библиотеки для разрешения внешних ссылок
Для Solaris 10 доступны два компилятора: GNU Compiler Collection (GCC) и SunStudio. Оба компилятора доступны бесплатно, но на разных условиях: от компилятора GCC также доступны исходные тексты. Оба компилятора включают в себя компиляторы C (с поддержкой диалектов Kernigan & Ritchie, ANSI C, C99) и С++. Компилятор SunStudio также поддерживает директивы параллельного программирования OpenMP.
Для запуска компилятора языка C используется команда cc (SunStudio) или gcc (GCC). Для запуска компилятора C++ используются команды CC или g++. В дальнейших примерах мы будем использовать название команды cc; когда будут обсуждаться особенности других форм запуска компилятора, это будет оговариваться отдельно.
Команда cc - это управляющая программа, которая последовательно вызывает с помощью fork и exec другие программы, реализующие фазы процесса компиляции. Каждой фазе соответствует свои опции, и у каждой фазы свои сообщения об ошибках. Раздел ФАЙЛЫ на странице Руководства cc(1) указывает, где может быть найдена каждая исполняемая фаза. В общем случае, фазы процесса компиляции не должны вызываться явно. Их вызов осуществляет команда cc(1). Каждая фаза использует файлы или программные каналы для передачи своего вывода следующей фазе.
Формат команды сс
Команда cc имеет формат:
cc [опции] file1.c [file2.c …]
Обзор поддерживаемых опций будет приведен далее в этом приложении. Команде компилятора необходимо указать один или несколько файлов. В зависимости от расширения файла, компилятор автоматически определяет, что с ним следует делать: файлы с расширением .c обрабатываются всеми фазами компиляции, начиная с препроцессора и транслятора, файлы с расширением .s — начиная с ассемблера, файлы с расширениями .o, .a и .so сразу передаются редактору связей.
Простой способ собрать программу из нескольких модулей исходного текста — это передать компилятору список всех этих модулей. Однако это приводит к тому, что при каждом вызове такой команды все исходные файлы будут компилироваьтся заново. При разработке и отладке программы обычно ее приходится перекомпилировать много раз; обычно при каждой пересборке меняется только часть файлов, часто даже только один файл. Фаза трансляции занимает много времени, поэтому невыгодно перекомпилировать те файлы исходного текста, которые не менялись.
При компиляции программ, состоящих из большого количества файлов исходных текстов, обычно каждый файл компилируют с ключом -c (этот ключ приводит к тому, что редактор связей не вызывается, зато сохраняется объектный модуль в файле с расширением .o), а затем вызывают редактор связей отдельным вызовом команды cc. Это позволяет сэкономить время компиляции, вызывая компилятор только для тех файлов, которые были изменены, и используя старые объектные модули от тех файлов, которые не изменялись. Обычно для координации такой раздельной компиляции используют программу make(1), которая будет кратко описана далее в этом приложении.
Процесс Си-компиляции - Фаза препроцессора
Первая фаза компиляции — препроцессор языка C. Выходные данные препроцессора — это еще ASCII текст (операторы языка C). Все, что делает препроцессор, это текстовые вставки и замены
Следующие опции позволяют завершить процесс компиляции после препроцессора:
-P - выходные данные записываются в файл name.i.
-E - выходные данные записываются в стандартный вывод.
Замечание: Команда, исполняющая эту фазу, описана на странице cpp(1) Справочного руководства пользователя.
Директивы препроцессора
Все операторы препроцессора - это полные строки. Директивы препроцессора начинаются с символа # и заканчиваются <NEWLINE> или началом комментария. Можно соединить несколько строк вместе, набрав обратную косую черту (\) в конце соединяемых строк. Перед символом # или после него может стоять один или несколько пробелов или табуляций, но не должно быть символов, не являющихся пробелами
Зарезервированные препроцессором слова (например, define или include), расположены непосредственно справа от #, возможно (но не обязательно) отделенные от этого символа одним или несколькими пробелами.
В директиве #include может быть задано абсолютное или относительное путевое имя файла. Такой файл называют вставляемым файлом, файлом заголовка или файлом определений. Обычно имя файла заголовка имеет расширение .h, хотя препроцессор не выдвигает на этот счет никаких требований. В C++ также используют расширения .hpp или файлы заголовка без расширений.
#include "header.h" файл ищется в директории исходного файла, затем в стандартной директории /usr/include
#include <header.h> файл ищется только в /usr/include
#include "/usr/header.h" файл задается путевым именем
Вставляемые файлы обычно содержат директивы #define для констант и макроопределений, а также директивы #include для других файлов, описания типов, структур, классов и шаблонов C++, объявления глобальных переменных и функций. Достаточно часто вставляемые файлы используются для описания в одном месте структур и параметров, используемых в нескольких файлах исходных текстов.
Директивы #define часто используются, чтобы сделать программу более читабельной. Эти имена имеют такую же форму, как идентификаторы языка C. Чтобы выделить эти имена в тексте, их обычно набирают заглавными буквами. Область действия имени — от соответствующего #define до конца файла или до команды #undef, которая отменяет макроопределение.
Макроопределение имеет форму: #define macro_name(param_1,...,param_n) token_string. Списка параметров может не быть. Не должно быть пробела между macro_name и (.Макросы и символьные имена могут быть определены в терминах ранее определенных макросов и/или символов. Макроопределения также называют символами препроцессора.
Встретив macro_name в тексте, препроцессор заменяет его на token_string, осуществляя подстановку аргументов и выдавая ошибку, если количество переданных аргументов не соответствует заданному в объявлении. Препроцессор не проверяет соответствия выполненной подстановки синтаксису языка C.
Если SYMBOL дважды определяется с помощью #define без #undef между ними, то препроцессор может выводить предупреждение "SYMBOL redefined". При этом действует последнее определение.
Условная компиляция
Во время фазы препроцессора можно проверить какое-либо условие с помощью директив #if, #ifdef или #ifndef. Все три формы завершаются директивой #endif. Дополнительно, между началом и концом конструкции может появиться #else .
Условие оператора #if expression считается истинным, если expression имеет ненулевое значение.
Условие директивы #ifdef identifier считается истинным, если identifier определен при помощи директивы #define (при этом не имеет значения, как именно он был определен) и не был отменен при помощи директивы #undef.
Условие оператор #ifndef identifier считается истинным, если identifier не был определен.
#ifdef identifier — это сокращенная форма записи #if defined(identifier), а #ifndef — соответственно, #if !defined(identifier).
Если условие директивы #if, #ifndef или #ifdef истинно, операторы между #if и соответствующими #else или #endif обрабатываются (передаются на вход компилятору), а операторы между #else и #endif (если существуют) заменяются пустыми строками или директивой #line. Это необходимо, чтобы компилятор правильно определял номера строк исходного файла при выдаче отладочной информации и сообщений об ошибках.
Если проверяемое условие ложно, операторы между #if и соответствующим #else или #endif заменяются пустыми строками. Выполняются, если существуют, операторы между #else и #endif.
Выражение, используемое в #if может содержать в качестве переменных макроопределения (но не переменные языка C) и любые операции языка Си (логические, арифметические, отношения). Помните, что #if SYMBOL и #if SYMBOL != 0 вырабатывают одинаковый результат.
В следующем примере Z получит значение 10, если во время работы препроцессора X больше чем 3, или Y имеет ненулевое значение. X и Y — ранее определенные параметры:
#if X > 3 || Y #define Z 10 #else #define Z 20 #endif
Условная компиляция привносит гибкость в процесс компиляции. С ее помощью во время компиляции могут быть установлены определенные параметры программы, например, внедрен или удален отладочный код, или откомпилированы разные версии кода для разных компиляторов, ОС или аппаратных платформ.
Заранее определенные символы препроцессора
Препроцессор автоматически определяет ряд символов, которые могут быть использованы в директивах условной компиляции. Эти символы определяют
. Тип аппаратной архитектуры, например, i386 или sparc
. Версию ОС
. Версию компилятора и поддерживаемые компилятором диалекты языка
. Имя текущего обрабатываемого файла: __FILE__ заменяется именем текущего исходного файла, заключенным в двойные кавычки.
. Номер обрабатываемой строки: __LINE__ заменяется целым номером текущей строки в формате целого десятичного числа.
При задании некоторых ключей компиляции, например, -mt у компилятора SunStudio или -threads у GCC, препроцессор также может определять дополнительные символы, указывающие, что компилируется многопоточная программа.
Эти заранее определенные символы могут быть использованы, чтобы сделать программы на Си более переносимыми.
Опции препроцессора
Препроцессор вызывается автоматически на первой фазе процесса компиляции. Следующие опции распознаются командой компилятора (cc) и передаются препроцессору.
-P Работает только препроцессор (вывод пишется в файл с расширением .i)
-E Работает только препроцессор (вывод пишется в файл стандартного вывода)
-Dname=def Определяет идентификатор. Эквивалентно директиве #define name def в начале файла. Например: cc -DSIZE=10 -DSYM=5 -DDEBUG prog.c. Замечание: если =def опущено, это эквивалентно -Dname=1
-Uname Отменяет определение идентификатора name. Используется для отмены автоматически определяемых препроцессорных символов. Например: cc -E -Ui386 -Du370 prog.c.
-Idir Добавляет dir в список директорий, в которых будут искаться вставляемые файлы.
Для директивы #include "file.h" поиск ведется сначала в директории, где хранятся исходные файлы, затем в dir, и в последнюю очередь в стандартной директории /usr/include.
Для директивы #include <file.h> поиск ведется сначала в dir и затем в стандартной директории.
Пример:
cc -I$HOME/myproject/include -I..prog.c
-C Не удаляет комментарии
Сообщения об ошибках препроцессора
Препроцессор может выдавать сообщения об ошибках. Например:
1. Использование макроса со слишком малым или слишком большим количеством аргументов приведет к выводу сообщения argument mismatch.
2. Неправильное написание ключевого слова препроцессора (например, #inclde) вызовет сообщение undefined control.
3. Использование имени несуществующего файла или неправильно написанное имя файла в #include приведет к сообщению Can't find include file...
4. Отсутствие двойных кавычек или < > вокруг имени файла в #include вызовет сообщение bad include syntax.
Процесс компиляции - Фаза транслятора
Второй фазой процесса компиляции является транслятор или компилятор. Входом для него является программа, обработанная препроцессором. Выход — программа на ассемблере, "родном" для целевого компьютера.
Характеристики
Тип файла документ
Документы такого типа открываются такими программами, как Microsoft Office Word на компьютерах Windows, Apple Pages на компьютерах Mac, Open Office - бесплатная альтернатива на различных платформах, в том числе Linux. Наиболее простым и современным решением будут Google документы, так как открываются онлайн без скачивания прямо в браузере на любой платформе. Существуют российские качественные аналоги, например от Яндекса.
Будьте внимательны на мобильных устройствах, так как там используются упрощённый функционал даже в официальном приложении от Microsoft, поэтому для просмотра скачивайте PDF-версию. А если нужно редактировать файл, то используйте оригинальный файл.
Файлы такого типа обычно разбиты на страницы, а текст может быть форматированным (жирный, курсив, выбор шрифта, таблицы и т.п.), а также в него можно добавлять изображения. Формат идеально подходит для рефератов, докладов и РПЗ курсовых проектов, которые необходимо распечатать. Кстати перед печатью также сохраняйте файл в PDF, так как принтер может начудить со шрифтами.