Они могут указывать на архитектуру модели компьютера, отдельную модель
компьютера, на операционную систему, ее версию или на специфические
возможности конфигурации. Все это может сочетаться в одном макросе. В
отличие от макросов, утверждения состоят из четко поставленного вопроса и
ответа на него. Вопрос обычно называется "утверждением". Утверждение
выглядит следующим образом:
#PREDICATE (ANSWER)
Для имени PREDICATE следует использовать правильно сформированный
идентификатор. Значением ANSWER может быть любая последоватльность слов.
Здесь все символы являются значимыми, за исключением пробелов, расположенных
в начале и в конце ANSWER. Различия в пробелах в середине значения
игнорируются. Не разрешается использовать символ ')' в значении ANSWER.
Далее приведен пример условия, проверяющего является ли ответ ANSWER
утверждением PREDICATE:
#if #PREDICATE (ANSWER)
Для одного утверждения может существовать несколько ответов. Если ответ
упущен при определении утверждения, то следует проверять, существует ли
у данного утверждения какой-нибудь ответ:
#if #PREDICATE
Большинство проверяемых утверждений являются заранее определенными.
GNU C предоставляет три заранее определенных утверждения: 'system', 'cpu'
и 'machine'. Утверждение 'system' используется для описания типа операционной
системы, 'cpu' - для описания архитектуры компьютера, а 'machine'
предоставляет дополнительную информацию о компьютере. Например, в системе
GNU будут верны следующие утверждения:
#system (gnu)
#system (mach)
#system (mach 3)
#system (mach 3.SUBVERSION)
#system (hurd)
#system (hurd VERSION)
а также возможно и другие. Альтернативные утверждения с более или менее
подробной информацией о версии системы помогут получить ответ на вопрос о
типе операционной системы.
В системе Unix существует утверждение '#system (unix)', а возможно
одно из следующих: `#system (aix)', `#system (bsd)', `#system (hpux)',
`#system (lynx)', `#system (mach)', `#system (posix)', `#system (svr3)',
`#system (svr4)', или `#system (xpg4)' вероятно с последующей информацией
о версии системы.
Другие значения для 'system' это '#system (mvs)' и '#system (vms)'.
Многие Unix С компиляторы предоставляют только один ответ на
утверждение 'system': '#system (unix)', если они вообще используют
утверждения.
Утверждение, ответ которого сосотоит из нескольких слов сильно
отличается от утверждений с ответом из одного слова. Например, утверждение
'system (mach 3.0)' не означает, что 'system (3.0)' - верно. Это также не
всегда означает, что 'system (mach)' тоже верно, но в GNU C последнее
утверждение может быть использовано.
В настоящий момент возможные значения утверждений для 'cpu' являются
'#cpu (a29k)', `#cpu (alpha)', `#cpu (arm)', `#cpu (clipper)', `#cpu
(convex)', `#cpu (elxsi)', `#cpu (tron)', `#cpu (h8300)', `#cpu
(i370)', `#cpu (i386)', `#cpu (i860)', `#cpu (i960)', `#cpu (m68k)',
`#cpu (m88k)', `#cpu (mips)', `#cpu (ns32k)', `#cpu (hppa)', `#cpu
(pyr)', `#cpu (ibm032)', `#cpu (rs6000)', `#cpu (sh)', `#cpu (sparc)',
`#cpu (spur)', `#cpu (tahoe)', `#cpu (vax)', `#cpu (we32000)'.
В С программе можно создавать свои утверждения с помощью директивы
'#assert' следующим образом:
#assert PREDICATE (ANSWER)
(следует заметить отсутствие симола '#' перед PREDICATE.)
При каждом выполнении этой директивы создается новый правильный ответ
для PREDICATE. При утверждении одного ответа предыдущие значения остаются в
силе. Единственный способ удалить утверждение - использовать директиву
'#unassert'. Эта директива имеет такой же систаксис как и '#assert'. Можно
удалить все утверждения для PREDICATE следующим образом:
#unassert PREDICATE
Также имеется возможность добавления или удаления утверждений с
помощью опций при вызове 'gcc' или 'cpp'.
5.6. Директивы '#error' и '#warning'
Директива '#error' вынуждает препроцессор сделать отчет о фатальной
ошибке. Все что следует после '#error' используется для сообщения.
Директива '#error' в теле условия, проверяющего комбинацию параметров,
не до конца поддерживаемых программой, используется для сообщения о
возможной ошибке. Например, если известно, что программа не совсем корректно
выполняется на системе Vax, то можно написать:
#ifdef __vax__
#error Won't work on Vaxen. See comments at get_last_object.
#endif
Если имеется несколько конфигурационных параметров, которые должны
быть указаны соответствующим образом при установке, можно использовать
условия для определения несоответствия и выдать сообщение об ошибке.
Например,
#if HASH_TABLE_SIZE % 2 == 0 || HASH_TABLE_SIZE % 3 == 0 \
|| HASH_TABLE_SIZE % 5 == 0
#error HASH_TABLE_SIZE should not be divisible by a small prime
#endif
Директива '#warning' аналогична директиве '#error', но приводит к тому,
что препроцессор выдает предупреждающее сообщение и продолжает обработку.
Все что, что следует после '#warning' используется для сообщения.
Эту директиву можно использовать в устаревших подключаемых файлах с
указанием на новую версию файла.
6. Комбинирование исходных файлов
Одна из основных задач С препроцессора - это передача информации
компилятору о месторасположении различных частей программы.
Код программы может формироваться из нескольких исходных файлов при
использовании директивы '#include'. Применение как директивы '#include',
так и условий с макросами приводит к изменению основного исходного файла.
Следует принимать во внимание значение нумерации строк С компилятором
(при сообщениях об ошибках) и отладчиком (например, GDB).
В С препроцессоре существует директива, позволяющая контролировать эту
возможность. Это может пригодиться в случае, если файлом ввода препроцессора
является файл вывода другой программы, такой как 'bison', которая
обрабатывает другой файл, являющийся основным исходным файлом. При подобной
обработке нумерация строк теряется.
Для устранения этого недостатка используется директива '#line', которая
позволяет указать номер строки реального исходного файла вместе с его именем.
Директива '#line' применяется в трех модификациях:
'#line LINENUM'
Здесь LINENUM это десятичная целая константа, указывающая, что следующая
строка является строкой исходного файла с номером LINENUM.
'#line LINENUM FILENAME'
Здесь LINENUM это десятичная целая константа, а FILENAME - строковая
константа, указывающая, что следующая строка является строкой исходного
файла с именем FILENAME, а ее номер - LINENUM. Значение FILENAME должно
быть заключено в двойные кавычки.
'#line ANYTHING ELSE'
Значение ANYTHING ELSE проверяется на наличие макро вызовов, которые
затем подставляются. Результатом должна быть десятичная целая константа,
за которой может следовать стороковая константа, как это рассмотрено выше.
Директива '#line' изменяет значения заранее определенных макросов
'__FILE__' и '__LINE__'.
Вывод препроцессора (который затем перенапрвляется в компилятор)
содержит директивы, подобные '#line', только они начинаются с символа '#',
в отличие от '#line', за которыми следует номер строки и имя файла.
7. Другие директивы препроцессора
В этом разделе рассматриваются три дополнительные директивы. Они
описываются для полноты и не являются часто используемыми.
"Пустая директива" состоит из символа '#', за которым следует символ
новой строки, причем между ними могут быть только пробелы (включая
комментарии). Пустая директива рассматривается как директива препроцессора и
не влияет на общий вывод. Основным значением этой директивы является то,
при обработке строки, содержащей символ '#', на вывод ничего не передается,
в отличие от выводимой строки, содержащей '#'.
Стандарт ANSI указывает, что директива '#pragma' имеет произвольное
значение, устанавливаемое при разработке. В препроцессоре GNU C она
не используется, за исключением значения '#pragma once'. Однако, она
остается при выводе препроцессора и может быть использована в процессе
компиляции.
Директива '#ident' используется для совместимости с некоторыми
системами. За ней следует строка с текстом. На некоторых системах этот
текст копируется в отдельное место объектного файла. Но в большинстве систем
он просто игнорируется и применение этой директивы не дает никакого эффекта.
В действительности, эта директива является единственной директивой,
используемой в подключаемых файлах и функционирует на тех системах,
которые ее поддерживают.
8. Вывод С препроцессора
Вывод С препроцессора выглядит примерно так же, как и ввод, только
все строки с директивами заменяются на пустые и комментарии заменяются
пробелами. Пробелы в середине строки не меняются. Однако пробел вставляется
после большинства макроподстановок.
Имя исходного файла и информация о номере строки передается в строках
следующей формы:
# LINENUM FILENAME FLAGS
которые вставляются в середину вводимого файла (но не в строчную или
символьную константу). Появление такой строки означает, что следующая
строка содержится в файле FILENAME и имеет порядковый номер LINENUM.
После имени файла следует нуль или более флагов, значения которых
могут быть '1', '2', '3' или '4'. Если флагов более одного, то они
разделяются пробелами. Далее следует описание этих флагов.
'1'
Указывает на начало нового файла.
'2'
Указывает на возврат в файл (после включения другого файла).
'3'
Указывает на то, что следующий текст исходит из системного
подключаемого файла, поэтому должны использоваться специальные
предупреждающие сообщения.
'4'
Указывает на то, что следующий текст должен рассматриваться как С код.
9. Вызов GNU С Препроцессора
Обычно нет необходимости вызывать С препроцессор отдельно: он вызывается
автоматически С компилятором. Однако иногда требуется запустить препроцессор
без компилятора.
С препроцессор запрашивает два имени файла в качестве аргументов. Один