sem09 - препроцессор_ gcc (1114923), страница 2
Текст из файла (страница 2)
Предположим, что мы хотим написать макрос, который меняет местами значения двух своих аргументов произвольного типа. Макрос используется какпроцедура, то есть он не может встретиться в выражении. Такой макрос может выглядетьтак:#define swap(type,a,b) {type t = a; a = b; b = t;}Использоваться в программе он может, например, следующим образом:swap(int,x,y);. Поскольку в тексте программы swap выглядит как вызов функции, естественно ставить после него символ окончания оператора ;. Но предположим, чтовызов этого макроса используется в условном операторе, напримерif (cond)swap(int,x,y);elsefunc();При компиляции этого фрагмента программы будет получено сообщение о синтаксической ошибке.
В самом деле, после макроподстановки получимif (cond){int t = x; x = y; y = t;};elsefunc();4Составной оператор после if не требует символа ; в конце оператора, поэтому компилятор будет рассматривать ; после закрывающей фигурной скобки как пустой оператор ужепосле условного оператора, таким образом ключевое слово else оказывается не относящимся ни к какому условному оператору.Чтобы устранить этот недостаток нужно переписать макрос так, чтобы его тело было одним оператором, но после которого требовалась бы ;. Сделать это можно, используя оператор цикла do—while.#define swap(type,a,b) do{type t = a; a = b; b = t;}while(0)Оптимизирующий компилятор может заметить, что условие цикла всегда ложно, поэтомулишнего кода сгенерировано не будет.Осталась ещё одна неприятность: что будет, если в качестве одного из аргументов макроса будет указана переменная t, которая вполне может существовать в точке использованияэтого макроса? Мы можем использовать какое-нибудь сложное имя, вероятность использования которого в программе невелика, либо можем переложить задачу подбора уникальногоимени на пользователя макроса, добавив ещё один параметр — имя временной переменной.1.1.4 Директива #includeДиректива #include вставляет содержимое заданного файла вместо данной директивы.Директива может записываться в одной из трёх форм:#include <file>#include "file"#include macroВ первом случае файл с именем file ищется в стандартных каталогах компилятора, и еслион найден, его содержимое подставляется вместо директивы #include.
Пользователь имеет возможность добавлять свои каталоги к списку стандартных каталогов. Во втором случаефайл с именем file ищется сначала в текущем каталоге, и только если он не найден там,поиск продолжается в стандартных каталогах. В третьем случае выполняются макроподстановки, и после макроподстановок должна получиться директива #include либо в первой,либо во второй форме.Обычно в программы на языке Си с помощью директивы #include включаются так называемые заголовочные файлы, которые обычно имеют суффикс .h. В заголовочных файлах находятся определения типов данных, макросов, прототипы функций, внешние объявления переменных, то есть информация, необходимая для правильной компиляции программы,состоящей из нескольких исходных файлов.1.1.5 Директивы условной компиляцииДирективы условной компиляции позволяют включать или исключать части текста программы в зависимости от выполнения условия.
Фрагменты, использующие условную компиляцию, имеют следующий вид:#if <expr1><text1>#elif <expr2><text2>...5#elif <exprn><textn>#else<texte>#endifКаждый блок условной компиляции начинается с директив #if, #ifdef или #ifndefи заканчивается директивой #endif. Блоки условной компиляции могут вкладываться другв друга, но поскольку каждый завершается директивой #endif неоднозначности не возникает.В выражении, которое определяет условие условной компиляции, могут использоватьсяцелые литеральные значения и идентификаторы, определённые как макросы. Допускаются все операции языка Си, применимые к целым величинам.
Перед вычислением выражения выполняется макрорасширение всех использованных макросов. Если значение вычисленного выражения <expr1> не равно 0, то текст <text1> сохраняется, а все остальные<texti> заменяются на пустые строки. Если значение выражения <expr1> равно 0, а вблоке условной компиляции есть директивы #elif, вычисляются выражения <exprj>, итекст, соответствующий первому из них, которое дало ненулевой результат, сохраняется, аостальные тексты заменяются на пустые строки. Если ни одно из выражений не дало значения «истины», сохраняется текст, который следует за директивой #else, если она присутствует.В препроцессорных выражениях допустима унарная операция defined <name>,которое вырабатывает значение «истина», если <name> был ранее определён какмакрос. Директива условной компиляции #ifdef <name> эквивалентна директиве#if defined <name>, а директива условной компиляции #ifndef <name> эквивалентна директиве #if !defined <name>.Основное назначение директив условной компиляции — задавать фрагменты программы, которые должны или не должны компилироваться в зависимости от значения некоторогомакроса препроцессора.
Например, программа может компилироваться в двух режимах: отладочном и рабочем. Отладочный режим может обозначаться определением макроса DEBUG.Тогда мы можем определить макрос для отладочной печати, который в отладочном режимебудет раскрываться в некоторый оператор, выводящий отладочную печать, а в рабочем режиме — в пустую строку.#if defined DEBUG#define DPRINT(x) printf x#else#define DPRINT(x)#endifТогда отладочная печать добавляется в программу следующим образом:x = some_function();DPRINT(("x = %d\n", x));Обратите внимание, что аргумент макроса DPRINT заключён в двойные скобки.Другое применение условной компиляции — для фрагментов программ, которые выглядят по-разному в разных операционных системах.#if defined __MSDOS__<здесь фрагмент для MS-Dos>#elif defined __linux__6<а здесь - для Linux>#endifНаконец, условная компиляция может использоваться для комментирования большихфрагментов кода программы.
Как известно, комментарии в языке Си не могут вкладыватьсядруг в друга, поэтому невозможно закомментировать текст функции с помощью /* и */, еслион уже содержит такие комментарии. Тогда нужно использовать условную компиляцию:#if 0<some code to comment>#endifБлоки условной компиляции могут вкладываться друг в друга, поэтому закомментированный таким образом фрагмент кода может потом оказаться частью ещё большего отключённого фрагмента.1.2 Схема трансляции программыРассмотрим схему трансляции программы на языке Си, которая традиционно используется в системах Unix. Трансляция программы состоит из следующих этапов:1.
препроцессирование;2. трансляция в ассемблер;3. ассемблирование;4. компоновка.Традиционно исходные файлы программы на языке Си имеют суффикс имени файла .c,заголовочные файлы для программы на Си имеют суффикс .h. В файловых системах типаUnix регистр букв значим, и если, например, имя файла имеет суффикс .C, такой файл считается содержащим текст программы на языке Си++, и будет компилироваться компиляторомязыка Си++, а не Си.Препроцессирование. Препроцессирование уже было рассмотрено нами ранее. Препроцессор просматривает входной .c файл, исполняет в нём директивы препроцессора,включает в него содержимое других файлов, указанных в директивах #include и пр.В результате получается файл, который не содержит директив препроцессора, все используемые макросы раскрыты, вместо директив #include подставлено содержимое соответствующих файлов.
Файл с результатом препроцессирования обычно имеет суффикс .i,однако после завершения трансляции все промежуточные временные файлы удаляются, поэтому такой файл, как правило, никогда не виден пользователю. Результат препроцессирования называется единицей трансляции.Трансляция в ассемблер. Это — основная фаза работы. На вход подаётся одна единицатрансляции, а на выходе (при отсутствии синтаксических и семантических ошибок) выдаётсяфайл на языке ассемблера для (как правило) машины, на которой ведётся трансляция.
Файлс оттранслированной программой на языке ассемблера имеет суффикс имени .s, но точнотак же, как и результат работы препроцессора, он, как правило, не виден пользователю.Ассемблирование. На этой стадии работает ассемблер. Он получает на входе результат работы предыдущей стадии и генерирует на выходе объектный файл. Объектные файлы7традиционно имеют суффикс .o. Программа-ассемблер в системах Unix обычно называетсяas.Компоновка. Компоновщик получает на входе объектные файлы для каждой единицытрансляции, из которых состоит программа, подключает к ним стандартную библиотеку языка Си и библиотеки, указанные пользователем, и на выходе получает исполняемую программу. В системах Unix исполняемые двоичные программы не имеют никакого специальногосуффикса, например, оболочка-драйвер для компилятора GNU C называется просто gcc.Компоновщик (редактор связей) в системах Unix обычно называется ld.1.3 Запуск транслятора gccРассмотрим основные возможности транслятора GNU C.
Транслятор запускается командой gcc <files-and-options>. В командной строке задаётся список файлов, которые должны быть оттранслированы и объединены в один исполняемый файл. Какие операции необходимо выполнить с файлом — зависит от суффикса имени файла. Возможныесуффиксы перечислены в таблице 1. Если имя файла имеет нераспознанный суффикс, этоимя передаётся компоновщику..h Заголовочный файл на языке Си. Попытка трансляции такого файла вызывает сообщение об ошибке..c Файл на языке Си.