Введение
1. Общие пpеобpазования
2. Директивы Препроцессора
3. Подключаемые файлы
3.1. Использование подключаемых файлов.
3.2. Директива '#include'.
3.3. Как работает директива '#include'
3.4. Однократно подключаемые файлы
3.5. Подключаемые файлы и наследование
4. Макросы
4.1. Простые макросы
4.2. Макросы с аргументами
4.3. Заранее определенные макросы
4.3.1. Стандартные заранее определенные макросы
4.3.2. Нестандартные заранее определенные макросы
4.4. Стрингификация
4.5. Объединение
4.6. Удаление макросов
4.7. Переопределение макросов
4.8. Особенности использования макросов
4.8.1. Неправильно используемые конструкции
4.8.2. Нестандартная группировка арифметических выражений
4.8.3. Использование точки с запятой
4.8.4. Удвоение побочных эффектов
4.8.5. Рекурсивные макросы
4.8.6. Отдельная подстановка макро аргументов
4.8.7. Зависимые макросы
4.9. Символы newline в макроаргументах
5. Условия
5.1. Для чего используются условия
5.2. Синтаксис условий
5.2.1. Директива '#if'
5.2.2. Директива '#else'
5.2.3. Директива '#elif'
5.3. Сохранение удаленного кода для дальнейших ссылок
5.4. Условия и макросы
5.5. Утверждения
5.6. Директивы '#error' и '#warning'
6. Комбинирование исходных файлов
7. Другие директивы препроцессора
8. Вывод С препроцессора
9. Вызов GNU С Препроцессора
Введение
С пpепpоцессоp является пpоцессоpом макpокоманд и автоматически
используется С компилятоpом пеpед обpаботкой пpогpаммы для внесения в
нее некотоpых изменений. Пpепpоцессоp позволяет опpеделять макpосы,
использование котоpых помогает избегать пpименения сложных констpукций.
С пpепpоцессоp пpедоставляет несколько основных функций:
Включение в пpогpамму дополнительных файлов, содеpжащих опpеделения
pазличных функций.
Создание макpосов, котоpые являются сокpащениями для пpоизвольных
фpагментов исходного текста пpогpаммы, и их замена пpепpоцессоpом на
соответствующие макpоопpеделения во всей пpогpамме.
Условная компиляция. Используя специальные диpективы пpепpоцессоpа
имеется возможность включения или исключения частей пpогpаммы в зависимости
от pазличных условий.
Контpоль стpоки. Если пpи комбиниpовании или pеоpганизации исходных
файлов в какой-либо пpомежуточный файл, котоpый впоследствии компилиpуется,
используется отдельная пpогpамма, то имеется возможность пpименения
контpоля стpоки для пеpедачи инфоpмации компилятоpу о местоpасположении
каждой стpоки исходного текста пpогpаммы.
Различные С пpепpоцессоpы имеют некотоpые отличия. В данном pуководстве
pассматpивается GNU С пpепpоцессоp, совместимый с С компилятоpами. GNU
С пpепpоцессоp содеpжит дополнительный набоp возможностей, в дополнение к
тем, котоpые входят в стадаpт ANSI.
Стандаpт ANSI С запpещает использование многих констpукций, обычно
используемых в С пpогpаммах в настоящее вpемя. Такая несовместимость может
доставить некотоpые неудобства для пользователей, поэтому GNU С пpепpоцессоp
сконфигуpиpован так, что он по умолчанию использует подобные констpукции.
Гpубо говоpя, для pаботы с пpепpоцессоpом в стандаpте ANSI С, его следует
вызывать с опциями `-trigraphs', `-undef' и `-pedantic'.
1. Общие пpеобpазования
Большинство функций С пpепpоцессоpа являются необpатимыми, несмотpя на
то, что для выполнения каких-либо действий, пpепpоцессоpу указывают
специальные диpективы. (Диpективами пpепpоцессоpа являются стpоки,
начинающиеся с символа '#'.) Существует тpи пpеобpазования, котоpые
пpепpоцессоp выполняет для всех обpабатываемых файлов, даже пpи отсутствии
диpектив.
Все комментаpии С заменяются пpобелами.
Последовательности символов backslash-newline удаляются, вне
зависимости от их местоположения. Это позволяет пpеpывать длинные стpоки
в пpогpамме для ее офоpмления.
Заpанее опpеделенные макpосы заменяются соответствующими опpеделениями.
Пеpвые два пpеобpазования выполняются пpактически пеpед всеми
остальными пpоходами, включая гpамматический pазбоp, и пеpед обpаботкой
диpектив пpепpоцессоpа. Поэтому, можно в любом месте пpогpаммы pазбить
стpоку на несколько частей с помощью последовательностей backslash-newline
(кpоме ситуаций сиспользованием trigraph, см. ниже).
/*
*/ # /*
*/ defi
ne FO
O 10
20
является идентичным стpоке '#define FOO 1020'. Таким же обpазом можно
pазбить даже escape-последовательность. Напpимеp, можно pазбить стpоку
'"foobar"' на две части между '' и 'b' следующим обpазом:
"foo
bar"
Хотя существуют исключения. В текстовых константах для вставки символа
'' используется последовательность ''. Стандарт ANSI требует применения
подобных конструкций. (В действительности, в ANSI C не разрешается
разбивать текстовые константы на несколько строк, поэтому это не
считается проблемой.)
К тому же существуют исключения, касающиеся всех трех типов
преобразований.
Комментарии С и заданные имена макросов не распознаются в директиве
'#include', где имя файла ограничено символами ''.
Комментарии С и имена макросов никогда не распознаются в символьных и
строковых константах.
Последовательности backslash-newline не могут полностью использоваться
в ANSI триграфах. Триграфы преобразуются перед удалением последовательностей
backslash-newline.
Это исключение действительно, если для работы с триграфами используется
опция '-trigraph'.
2. Директивы Препроцессора
Большинство возможностей препроцессора реализуется с использованием
специальных директив.
Директивами препроцессора являются строки, начинающиеся с символа '#',
за которым следует идентификатор, называемый именем директивы. Разрешается
использование пробелов перед и после символа '#'.
Существует строгий набор директив. Программы не могут определять новые
директивы.
Некоторые директивы требуют наличия аргументов, которыми является
оставшаяся часть строки отделенная от имени директивы одним или несколькими
пробелами.
Обычно, директива препроцессора не может занимать более одной строки.
Хотя, она может быть разбита с помощью последовательности backslash-newline.
Комментарии, содержащие перенос строки, также разбивают директиву на
несколько строк, но перед обработкой директивы все комментарии заменяются
пробелами. Если символ переноса строки находится внутри символьной или
строковой константы, то препроцессор обрабатывает последующие строки, как
ничем не связанные с предыдущей.
Символ '#' и имя директивы не происходят от макрорасширения. Например,
если 'foo' является параметром директивы 'define', то это не значит, что
'#foo' это директива препроцессора.
3. Подключаемые файлы
Подключаемый файл это файл, содержащий определения функций и переменных,
а также макроопределения вместе с некоторыми исходными файлами. Для
использования в программе подключаемых файлов применяется директива
препроцессора '#include'.
3.1. Использование подключаемых файлов.
Подключаемые файлы используются для двух целей:
Системные подключаемые файлы используются для определения интерфейсов
к составляющим операционной системы. Они подключаются для предоставления
объявлений и определений, требуемых для работы с системными вызовами и
библиотеками.
Подключаемые файлы пользователя содержат определения для интерфейсов
между исходными файлами программы.
Включение подключаемого файла в программу дает такой же результат, как
при копировании этого файла в каждый исходный файл этой программы. Подобное
копирование занимает много времени и побуждает возникновение ошибок. При
использовании подключаемых файлов все объявления и определения переменных и
функций находятся в одном файле и при необходимости могут быть изменены.
Обычно подключаемые файлы заканчиваются на '.h' и следует избегать
использования других стандартов.
3.2. Директива '#include'.
Как файлы пользователя, так и системные файлы включаются в программу с
использованием директивы препроцессора '#include'. Она имеет три модификации:
'#include '
Эта модификация используется для подключения системных файлов. При ее
выполнении производится поиск файла с именем FILE в списке указанных заранее
каталогов, а затем в стандартном списке системных каталогов. С помощью
опции '-I' указываются каталоги для поиска подключаемых файлов. Опция
'-nostdinc' запрещает поиск в стандартных системных каталогах и производит
поиск только в указанных каталогах.
Синтаксис такой модификации директивы '#include' довольно специфичен,
потому как комментарии внутри '' не распознаются. Поэтому в строке
'#include ' последовательность символов '/*' не начинает комментарий,
а указанная директива включает в программу файл с именем 'x/*y'.
Аргумент FILE не может содержать символа '>', хотя он может содержать
символ '
'#include "FILE"'
Эта модификация применяется для подключаемых файлов для программ
пользователя. Сначала файл FILE просматривается в текущем каталоге, а затем
в каталогах для системных подключаемых файлов. Текущим каталогом является
каталог текущего обрабатываемого файла. Он просматривается в первую очередь,
так как предполагается, что в нем находятся файлы, имеющие отношение к
текущему обрабатываемому файлу. (Если указана опция '-I-', то текущий
каталог не просматривается.)
Аргумент FILE не может содержать символов '"'. Символы backslash
интерпретируются как отдельные символы, а не начало escape
последовательности. Таким образом, директива '#include "xny"' указывает
имя файла, содержащего три символа backslash.
'#include ANYTHING ELSE'
Эта модификация называется "вычисляемой директивой #include". Любая
директива '#include', не соответствующая ни одной из модификаций,
рассмотреных выше, является вычисляемой директивой. Строка ANYTHING ELSE
проверяется на наличие соответствующего макроса, значение которого затем
заменяет его название. Полученная в результате строка должна уже
в точности соответствовать одной из рассмотренных выше модификаций (то есть
имя подключаемого файла должно быть заключено в кавычки или угловые скобки).
Эта возможность позволяет определять макросы, что дает возможность
изменять имена подключаемых файлов. Эта возможность, например, используется
при переносе программ с одной операционной системы на другие, где требуются
разные подключаемые файлы.
3.3. Как работает директива '#include'
Директива '#include' указывает С препроцессору обработать указанный
файл перед обработкой оставшейся части текущего файла. Информация, выдаваемая
препроцессором, содержит уже полученные данные, за которыми следуют данные,
получаемые при обработке подключаемого файла, а за которыми, в свою очередь,
следуют данные, получаемые при обработке текста, следующего после директивы
'#include'. Например, дан следующий подключаемый файл 'header.h':
char *test ();
и основная программа с именем 'program.c', использующая этот файл.
int x;
#include "header.h"
main ()
{
printf (test ());
}
Данные, полученные при обработке программы 'program.c' будут выглядеть
следующим образом:
int x;
char *test ();
main ()
{
printf (test ());
}
Для подключаемых файлов нет ограничений на объявления и
макроопределения. Любой фрагмент С программы может быть включен в другой
файл. Подключаемый файл может даже содержать начало выражения,
заканчивающееся в исходном файле или окончание выражения, начало которого
находится в исходном файле. Хотя комметарии и строковые константы не могут
начинаться подключаемом файле и продолжаться в исходном файле. Не завершенный
комментарий, стороковая или символьная константа в подключаемом файле
приводят к возникновению ошибки в конце файла.
Подключаемый файл может содержать начало или окончание сиснтаксической
конструкции, такой как определение функции.
Срока, следующая за директивой '#include' всегда является пустой и
добавляется С препроцессором даже если подключаемый файл не содержит
завершающий символ перевода строки.
3.4. Однократно подключаемые файлы
Часто случается, что подключаемый файл включает в себя другой файл. Это
может привести к тому, что отдельный файл будет подключаться неоднократно,
что может привести к возникновению ошибок, если файл определяет типы
структур или определения типов. Поэтому следует избегать многократного
подключения файлов.
Обычно это достигается путем заключения в условие всего содержимого
этого файла, как показано ниже:
#ifndef FILE_FOO_SEEN
#define FILE_FOO_SEEN
Сам файл
#endif /* FILE_FOO_SEEN */
Макрос 'FILE_FOO_SEEN' указывает на то, что файл уже однажды вкючался.
В подключаемых файлах пользователя макрос не должен начинаться с символа
'_'. В системных подключаемых файлах его имя не должно начинаться с символа
'__' во избежание возникновения конфликтов с программами пользователя. Каким
бы ни был файл, имя макроса должно содержать имя файла и некоторый
дополнительный текст во избежание вознкновения конфликтов с другими
подключаемыми файлами.
Препроцессор GNU C построен таким образом, что обработке подключаемого
файла он проверяет наличие определенных конструкций и наиболее рационально
их обрабатывает. Препроцессор специально отмечает полное вложение файла в
условие '#ifndef'. Если в подключаемом файле содержится директива '#include',
указывающая на обрабатываемый файл, или макрос в директиве '#ifndef' уже
определен, то обрабатываемый файл полностью игнорируется.
Существует также специальная директива, указывающая препроцессору, что
файл должен быть включен не более одного раза. Эта директива называется
'#pragma once'. Она использовалась в дополнение к директиве '#ifndef' и
в настоящее время она устарела и не должна прменяться.
В объектно ориентированном языке С существует модификация директивы
'#include', называемая '#import', которая используется для вкючения файла
не более одного раза. При использовании директивы '#import' вместо
'#include' не требуется наличия условных оборотов для предотвращения
многократной обработки файла.
3.5. Подключаемые файлы и наследование
"Наследование" это то, что происходит, когда какой либо объект или
файл образует некоторую часть своего содержимого путем виртуального
копирования из другого объекта или файла. В случае подключаемых С файлов
наследование означает, что один файл включает другой файл, а затем заменяет
или добавляет что-либо.
Если наследуемый подключаемый файл и основной подключаемый файл имеют
различные имена, то такое наследование называется прямым. При этом
используется конструкция '#include "BASE"' в наследуемом файле.
Иногда необходимо чтобы у наследуемого и основного подключаемого файла
были одинаковые имена.
Например, предположим, что прикладная программа использует системный
подключаемый файл 'sys/signal.h', но версия файла '/usr/include/sys/signal.h'
на данной системе выполняет того, что требуется в прикладной программе.
Будет удобнее определить локальную версию, возможно с именем
'/usr/local/include/sys/signal.h' для замены или добавления к версии,
поставляемой с системой.
Это можно выполнить с применением опции '-I.' при компиляции, а также
созданием файла 'sys/signal.h' который выполняет требуемые программе функции.
Но сделать так, чтобы этот файл включал стандартный файл 'sys/signal.h' не
так просто. При включении строки '#include ' в этот файл
произойдет подключение новой версии файла, а не стандартной системной версии.
Это приведет к рекурсии и ошибке при компиляции.
При использовании директивы `#include '
нужный файл будет найден, но этот способ является не эфективным, так как
содержит полный путь к системному файлу. Это может отразиться на содержании
системы, так как это означает, что любые изменения местоположения системных
файлов потребуют дополнительных изменений где-либо еще.
Более эффективным решением этой проблемы является применение директивы
'#include_next', которая используется для подключения следующего файла с
таким же именем. Эта директива функционирует также как и директива '#include'
за исключением поиска требуемого файла. Она начинает поиск списка каталогов
подключаемых файлов после каталога, где был найден текущий файл.
Предположим была указана опция '-I /usr/local/include', а список
каталогов для поиска включает '/usr/include'. Также предположим, что оба
каталога содержат файл с именем 'sys/signal.h'. Директива
'#include ' найдет нужный файл под каталогом
'/usr/local/include'. Если этот файл содержит строку
'#include_next ', то поиск будет возобновлен после предыдущего
каталога и будет найден файл в каталоге '/usr/include'.
4. Макросы
Макрос это тип сокращения, который можно заранее определить и
использовать в дальнейшем. Существует довольно много возможностей, связанных
с использованием макросов в С препроцессоре.
4.1. Простые макросы
"Простой макрос" это тип сокращения. Это идентификатор, который
используется для представления фрагмента кода.
Перед использованием макроса его необходимо определить с помощью
директивы '#define', за которой следует название макроса и фрагмент кода,
который будет идентифицировать этот макрос. Например,
#define BUFFER_SIZE 1020
определяет макрос с именем 'BUFFER_SIZE', которому соответствует текст
'1024'. Если где-либо после этой директивы встретится выражение в следующей
форме:
foo = (char *) xmalloc (BUFFER_SIZE);
то С препроцессор определит и заменит макрос 'BUFFER_SIZE' на его значение и
в результате получится
foo = (char *) xmalloc (1020);
Использование прописных букв в названиях макросов является стандартным
соглашением и повышает читабельность программ.
Обычно, макроопределением должна быть отдельная строка, как и при
использовании всех директив препроцессора. (Длинное макроопределение можно
разбить на несколько строк с применением последовательности
backslash-newline.) Хотя существует одно исключение: символы перевода
строки могут быть вкючены в макроопределение если они находятся в строковой
или символьной константе, потому как макроопределение не может содержать
каких-либо специальных символов. Макроопределение автоматически дополняется
соответствующим специальным символом, который завершает строчную или
символьную константу. Комментарии в макроопределениях могут содержать символы
перевода строки, так как это ни на что не влияет, потому как все комментарии
полностью заменяются пробелами вне зависимости от того, что они содержат.
В отличие от выше сказанного, не существует никаких ограничений на
значение макроса. Скобки не обязательно должны закрываться. Тело макроса не
обязательно должно содержать правильный С код.
Препроцессор С обрабатывает программу последовательно, поэтому
макроопределения вступают в силу только в местах, где они используются.
Поэтому, после обработки следующих данных С препроцессором
foo = X;
#define X 4
bar = X;
получится такой результат
foo = X;
bar = 4;
После подстановки препроцессором имени макроса, тело макроопределения
добавляется к началу оставшихся вводимых данных и происходит проверка на
продолжение вызовов макросов. Поэтому тело макроса может содержать ссылки
на другие макросы. Например, после выполнения
#define BUFSIZE 1020
#define TABLESIZE BUFSIZE
значением макроса 'TABLESIZE' станет в результате значение '1020'.
Это не является тем же, что и определение макроса 'TABLESIZE' равным
значению '1020'. Директива '#define' для макроса 'TABLESIZE' использует в
точности те данные, которые были указаны в ее теле и заменяет макрос
'BUFSIZE' на его значение.
4.2. Макросы с аргументами
Значение простого макроса всегда одно и то же при каждом его
использовании. Макросы могут быть более гибкими, если они принимают
аргументы. Аргументами являются фрагменты кода, которые прилагаются при
каждом использовании макроса. Эти фрагменты включаются в расширение макроса
в соответствии с указаниями в макроопределении.
Для определения макроса, использующего аргументы, применяется директива
'#define' со списком имен аргументов в скобках после имени макроса. Именами
аргументов могут быть любые правильные С идентификаторы, разделенные запятыми
и, возможно, пробелами. Открывающаяся скобка должна следовать сразу же после
имени макроса без каких-либо пробелов.
Например, для вычисления минимального значения из двух заданных можно
использовать следующий макрос:
#define min(X, Y) ((X) < (Y) ? (X) : (Y))>
Для применения макроса с аргументами нужно указать имя макроса, за
которым следует список аргументов, заключенных в скобки и разделенных
запятыми. Количество принимаемых аргументов должно соответствовать количеству
указываемых. Например, макрос 'min' можно использовать так: 'min (1, 2)' или
'min (x + 28, *p)'.
Значение макроса зависит от используемых аргументов. Каждое имя
аргумента во всем макроопределении заменяется на значения соответствующих
указанных аргументов. При использовании макроса 'min', рассмотренного ранее,
следующим образом:
min (1, 2)
будет получен следующий результат:
((1) < (2) ? (1) : (2))>
где '1' заменяет 'X', а '2' заменяет 'Y'.
При указании аргументов, скобки должны закрываться, а запятая не должна
завершать аргумент. Однако, не существует каких либо ограничений на
использование квадратных или угловых скобок. Например
macro (array[x = y, x + 1])
передает макросу 'macro' два аргумента: 'array[x = y' и 'x + 1]'.
После подстановки указанных аргументов в тело макроса, полученный в
результате текст добавляется к началу оставшихся данных и производится
проверка на наличие других вызовов макросов. Поэтому указываемые аргументы
могут содержать ссылки к другим макросам как с аргументами, так и без, а
также к тому же макросу. Тело макроса также может включать ссылки к другим
макросам. Например, макрос 'min (min (a, b), c)' заменяется следующим
текстом:
((((a) < (b) ? (a) : (b))) < (c)>
? (((a) < (b) ? (a) : (b)))>
: (c))
(Срока разбита на три для ясности и в действительности она не разбивается.)
Если макрос 'foo' принимает один аргумент и нужно передать ему пустой
аргумент, то в скобках следует указать по крайней мере один пробел:
'foo ( )'. Если пробел не указывать, а макрос 'foo' требует один аргумент,
то произойдет ошибка. Для вызова макроса, не принимающего аргументы, можно
использовать конструкцию 'foo0()' как рассмотрено ниже:
#define foo0()...
Если используется имя макроса, за которым не следует открывающаяся
скобка (после удаления всех следующих пробелов, символов табуляции и
комментариев), то это не является вызовом макроса и препроцессор не изменяет
текст программы. Поэтому возможно использование макроса, переменной и функции
с одним именем и в каждом случае можно изменять, когда нужно применить макрос
(если за именем следует список аргументов), а когда - переменную или функцию
(если список аргументов отстутствует).
Подобное двойственное использование одного имени может привести к
осложнениям и его следует избегать, за исключением случаев, когда оба
значения являются синонимами, то есть когда под одним именем определена
функция и макрос и оба выполняют одинаковые действия. Можно рассматривать
это имя как имя функции. Использование имени не для ссылки (например, для
получения адреса) приведет к вызову функции, в то время как ссылка приведет
к замене имени на значение макроса и в результате будет получен более
эффективный но идентичный код. Например, используется функция с именем
'min' в том же исходном файле, где определен макрос с тем же именем.
Если написать '&min' без списка аргументов, то это приведет к вызову функции.
Если же написать 'min (x, bb)' со списком аргументов, то вместо этого будет
произведена замена на значение соответствующего макроса. Если использовать
конструкцию '(min) (a, bb)', где за именем 'min' не следует открывающаяся
скобка, то будет произведен вызов функции 'min'.
Нельзя определять простой макрос и макрос с аргументами с одним именем.
В определении макроса с аргументами список аргументов должен следовать
сразу после имени макроса без пробелов. Если после имени макроса стоит
пробел, то макрос определяется без аргументов, а остальная часть строки
становится значением макроса. Причиной этому является то, что довольно
часто определяются макросы без аргументов. Определение макросов подобным
образом позволяет выполнять такие операции как
#define FOO(x) - 1 / (x)
(где определяется макрос 'FOO', принимающий один аргумент и добавляет минус
к числу, обратному аргументу) или
#define BAR (x) - 1 / (x)
(где определяется макрос 'BAR' без аргументов и имеющий постоянное значение
'(x) - 1 / (x)').
4.3. Заранее определенные макросы
Некоторые простые макросы являются заранее определенными. Их можно
применять без предварительного определения. Они разделяются на два класса:
стандартные макросы и системно-зависимые макросы.
4.3.1. Стандартные заранее определенные макросы
Стандартные заранее определенные макросы могут применяться вне
зависимости от используемой платформы или операционной системы на которой
функционирует GNU C. Их имена начинаются и заканчиваются двойным символом
подчеркивания. Все макросы в следующем списке до '__GNUC__' являются
стандартизированными ANSI C. Остальные макросы являются расширениями GNU C.
'__FILE__'
Этот макрос заменяется на имя текущего исходного файла в форме строковой
константы С. Возвращаемым именем является одно из указанных в директиве
'#include' или имя основного исходного файла.
'__LINE__'
Этот макрос заменяется на номер текущей строки в форме десятичной целой
константы. В то время как он называется заранее определенным макросом, его
значение меняется динамически.
Этот макрос и макрос '__FILE__' используются при генерировании сообщения
об ошибке для вывода несоответствия, определенного программой. Сообщение
может содержать номер строки исходного файла где была обнаружена ошибка.
Например,
fprintf (stderr, "Internal error: "
"negative string length "
"%d at %s, line %d.",
length, __FILE__, __LINE__);
Директива '#include' изменяет значения макросов '__FILE__' и '__LINE__'
на соответствующие исходному файлу. В конце этого файла, если это был
подключаемый файл, значения '__FILE__' и '__LINE__' становятся теми, какими
они были до директивы '#include' (только значение '__LINE__' увеличивается
на единицу, так как затем обрабатывается строка, следующая за директивой
'#include').
Значения '__FILE__' и '__LINE__' изменяются при использовании директивы
'#line'.
'__DATE__'
Этот макрос заменяется на строчную константу, которая указывает дату
запуска препроцессора. Эта константа содержит одинадцать символов и
выглядит примерно так '"Jan 29 1987"' или '"Apr 1 1905"'.
'__TIME__'
Этот макрос заменяется на строковую константу, которая указывает время
запуска препроцессора. Константа содержит восемь символов и выглядит
примерно так: '"23:59:01:'.
'__STDC__'
Этот макрос заменяется на константу со значением 1 для указания, что
это С стандарта ANSI.
'__STDC_VERSION__'
Этот макрос заменяется на номер версии стандарта С, длинной целой
константой в форме 'YYYYMML', где YYYY и MM год и месяц выхода версии
стандарта. Это указывает на версию стандарта С, к которой относится
препроцессор.
'__GNUC__'
Этот макрос определен тогда и только тогда, когда используется GNU C.
Он определен только тогда используется полный GNU C компилятор. Если
вызвать препроцессор отдельно, то этот макрос будет не определен. Его
значение указывает на основной номер версии GNU CC ('1' для версии 1 GNU CC,
которая уже является устаревшей, и '2' для версии 2).
'__GNUC_MINOR__'
Этот макрос содержит дополнительный номер версии компилятора. Он может
быть использован при работе с отличительными возможностями различных выпусков
компилятора.
'__GNUG__'
Компилятор GNU C определяет этот макрос если компилируемым языком
является С++.
'__cplusplus'
Стандарт ANSI для С++ раньше требовал определения этой переменной.
Хотя ее наличие больше не требуется, в GNU C++ она все еще определяется, как
и в других известных компиляторах С++. Этот макрос может быть использован
для определения каким компилятором был скомпилирован заголовок (С или С++).
'__STRICT_ANSI__'
Этот макрос определяется тогда и только тогда, когда при вызове GNU C
указывается опция '-ansi'. Он определяется как пустая строка.
'__BASE_FILE__'
Этот макрос заменяется на имя основного исходного файла в форме
строковой константы С. Это исходный файл, указываемый в качестве параметра
при вызове компилятора С.
'__INCLUDE_LEVEL__'
Этот макрос заменяется на десятичную целую константу, которая указывает
на уровень вложенности подключаемых файлов. Его значение увеличивается на
единицу при обработке директивы '#include' и уменьшается на единицу при
завершении обработки каждого файла. Начальное значение для файлов,
указываемых в командной строке при вызове компилятора является равным нулю.
'__VERSION__'
Этот макрос заменяется сторокой, указывающей номер версии GNU C.
Обычно это последовательность десятичных чисел, разделенных точками.
Например '"2.6.0"'.
'__OPTIMIZE__'
Этот макрос определяется в оптимизирующих компиляторах. Если но
определен, то это приводит к созданию в подключаемых файлах GNU
альтернативных макроопределений для некоторых функций из системных библиотек.
Проверка или использование значения этого макроса не имеет особого смысла,
до тех пор, пока не будет полной уверенности в том, что программы будут
выполняться с таким же эффектом.
'__CHAR_UNSIGNED__'
Этот макрос определяется тогда и только тогда, когда тип данных 'char'
является беззнаковым. Он реализован для правильного функционирования
подключаемого файла 'limit.h'. Не следует использовать этот макрос. Вместо
этого можно использовать стандартные макросы, определенные в файле 'limit.h'.
Препроцессор использует этот макрос для определения необходимости в
добавлении знакового бита в больших восьмеричных символьных константах.
'__REGISTER_PREFIX__'
Этот макрос заменяется на сроку, описывающую префикс, добавляемый к
обозначению регистров процессора в ассемблерном коде. Он может использоваться
для написания ассемблерного кода, функционирующего в различных оболочках.
Например, в оболочке 'm68k-aout' производится замена на строку '""', а
в оболочке 'm68k-coff' макрос заменяется на строку '"%"'.
'__USER_LABEL_PREFIX__'
Этот макрос заменяется на строку, описывающую префикс, добавляемый к
меткам пользователя в ассемблерном коде. Он может использоваться для
написания ассемблерного кода, функционирующего в различных оболочках.
Например, в оболочке 'm68k-out' он заменяется на строку '" "', а в оболочке
'm68k-coff' - на строку '""'.
4.3.2. Нестандартные заранее определенные макросы
Обычно С препроцессор имеет несколько заранее определенных макросов,
значения которых различаются в зависимости от используемой платформы и
операционной системы. В данном руководстве не представляется возможным
рассмотреть все макросы. Здесь описаны только наиболее типичные из них.
Для просмотра значений заранее определенных макросов можно воспользоваться
командой 'cpp -dM'.
Некоторые нестандартные заранее определенные макросы более или менее
подробно описывают тип используемой операционной системы. Например,
'unix'
Этот макрос обычно определен на всех системах Unix.
'BSD'
Этот макрос определен на последних версиях системы Berkley Unix
(возможно только в версии 4.3).
Другие макросы описывают тип центрального процесора. Например,
'vax'
Определен на Vax компьютерах.
'mc68000'
Определен на большинстве компьютеров, использующих процессор Motorola
68000, 68010 или 68020.
'm68k'
Также определен на большинстве компьютеров с процессором 68000, 68010
или 68020. Хотя некоторые разработчики используют 'mc68000', а некоторые -
'm68k'. Некоторые заранее определяют оба макроса.
'M68020'
Определен на некоторых системах с процессором 68020 в дополнение к
макросам 'mc68000' и 'm68k', которые являются менее специфичными.
'_AM29K'
'_AM29000'
Определены на компьютерах с процессорами из семейства AMD 29000.
'ns32000'
Определен на компьютерах, использующих процессоры серии National
Semiconductor 32000.
Другие нестандартные макросы описывают изготовителей компьютерных
систем. Например,
'sun'
Определен на всех моделях компьютеров Sun.
'pyr'
Определен на всех моделях компьютеров Pyramid.
'sequent'
Определен на всех моделях компьютеров Sequent.
Эти заранее определенные символы являются не только нестандартными, но
они к тому же не соответствуют стандарту ANSI, потому что их имена не
начинаются с символа подчеркивания. Поэтому опция '-ansi' запрещает
определение этих символов.
Это приводит к тому, что опция '-ansi' становится бесполезной, так как
большое количество программ зависит от нестандартных заранее определенных
символов. Даже системные подключамые файлы проверяют их значения и
генерируют неправильные объявления в случае если требуемые имена не
определены. В результате очень мало программ компилируется с опцией '-ansi'.
Что же нужно сделать в ANSI C программе для того, чтобы проверить тип
используемого компьютера?
Для этой цели GNU C предоставляет параллельную серию символов, имена
которых состоят из обычных символов с добавлением строки '__' с начала и
с конца. Таким образом символ '__vax__' используется на системах Vax, и
так далее.
Набор нестандартных заранее определенных символов в GNU C препроцессоре
изменяется (при компиляции самого компилятора) с помощью макроса
'CPP_PREDEFINES', которым является строка, состоящая из опций '-D',
разделенных пробелами. Например, на системе Sun 3 используется следующее
макроопределение:
#define CPP_PREDEFINES "-Dmc68000 -Dsun -Dunix -Dm68k"
Этот макрос обычно указывается в файле 'tm.h'.
4.4. Стрингификация
"Стрингификация" означает преобразование фрагмента кода в строковую
константу, которая содержит текст этого фрагмента кода. Например, в результате
стрингификации 'foo (z)' получается '"foo (z)"'.
В С препроцессоре, стрингификация является опцией, используемой при
замене аргументов в макросе макроопределением. При появлении имени аргумента
в теле макроопределения, символ '#' перед именем аргумента указывает на
стрингификацию соответствующего аргумента при его подстановке в этом месте
макроопределения. Этот же аргумент может быть заменен в другом месте
макроопределения без его стрингификации, если перед именем аргумента нет
символа '#'.
Вот пример макроопределения с использованием стрингификации:
#define WARN_IF(EXP)
do { if (EXP)
fprintf (stderr, "Warning: " #EXP "n"); }
while (0)
Здесь аргумент 'EXP' заменяется один раз обычным образом (в конструкции
'if'), а другой - с использованием стрингификации (аргумет функции
'fprintf'). Конструкция 'do' и 'while (0)' является реализацией макроса
'WARN_IF (ARG);'.
Возможности срингификации ограничены до преобразования одного макро
аргумента в одну строковую константу: не с