netlib.narod.ru< Назад | Оглавление | Далее >

4.11. Препроцессор языка Си

Некоторые возможности языка Си обеспечиваются препроцессором, который работает на первом шаге компиляции. Наиболее часто используются две директивы: #include, вставляющая содержимое некоторого файла во время компиляции, и #define, заменяющая одни текстовые последовательности на другие. В этом параграфе также обсуждаются условная компиляция и макросы с аргументами.

4.11.1. Включение файла

Включение файлов (помимо других полезных вещей) позволяет легко управлять наборами директив #define и объявлений. Любая строка вида

    #include "имя-файла"

или

    #include <имя-файла>

заменяется содержимым файла с именем имя-файла. Если имя файла заключено в двойные кавычки, то, как правило, файл ищется среди исходных файлов программы; если такового не оказалось или имя файла заключено в угловые скобки < и >, то поиск осуществляется по определяемым реализацией правилам. Включаемый файл сам может содержать в себе строки #include.

Часто исходные файлы начинаются с нескольких строк #include, ссылающихся на файлы, содержащие общие директивы #define, объявления extern или прототипы нужных библиотечных функций из заголовочных файлов вроде <stdio.h>. (Строго говоря, эти включения не обязательно являются файлами; технические детали того, как осуществляется доступ к заголовкам, зависят от конкретной реализации.)

Директва #include — хороший способ собрать вместе объявления большой программы. Он гарантирует, что все исходные файлы будут пользоваться одними и теми же определениями и объявлениями переменных, благодаря чему предотвращаются особенно неприятные ошибки. Естественно, при внесении изменений во включаемый файл все зависимые от него файлы должны перекомпилироваться.

4.11.2. Макроподстановка

Директива макроподстановки имеет вид:

    #define имя    замещающий_текст

Макроподстановка используется для простейшей замены: во всех местах, где встречается имя, вместо него будет помещен замещающий_текст. Имена в #define задаются по тем же правилам, что и имена обычных переменных. Замещающий текст может быть произвольным. Обычно замещающий текст целиком помещается в строке, в которой расположено слово #define, однако длинные определения можно разбивать на несколько строк, поставив в конце каждой продолжаемой строки обратную наклонную черту \. Область видимости имени, определенного директивой #define, простирается от определения до конца файла. В определении макроподстановки могут использоваться предшествующие ему макроопределения. Подстановка осуществляется только для тех имен, которые расположены вне текстов заключенных в кавычки и не являются частью другого слова. Например, если YES определено с помощью директивы #define, то никакой подстановки в printf("YES") или в YESMAN выполнено не будет.

Любое имя можно определить с произвольным замещающим текстом. Например,

    #define forever for(;;)    /* бесконечный цикл */

определяет новое слово forever для бесконечного цикла.

Можно определить макрос с аргументами, чтобы замещающий текст варьировался в зависимости от задаваемых параметров. Например, определим max следующим образом:

    #define max(A, B) ((A) > (B) ? (A) : (B))

Хотя обращения к max выглядят как обычные обращения к функции, они будут вызывать только текстовую замену. Каждый формальный параметр (в данном случае A и B) будет заменяться соответствующим ему аргументом. Так, строка

    x = max(p+q, r+s);

будет заменена на строку

    x = ((p+q) > (r+s) ? (p+q) : (r+s));

Поскольку аргументы просто подставляются в текст, указанное определение max подходит для данных любого типа, так что не нужно писать различные варианты max для данных разных типов, как это было бы в случае определения функции.

Если вы внимательно проанализируете работу max, то обнаружите некоторые подводные камни. Выражения вычисляются дважды, и если они вызывают побочный эффект (из-за инкрементных операций или функций ввода-вывода), это может привести к нежелательным последствиям. Например,

    max(i++, j++)            /* НЕВЕРНО */

вызовет увеличение i и j дважды. Кроме того, следует позаботиться о скобках, чтобы обеспечить нужный порядок вычислений. Задумайтесь, что случится, если при определении

    #define square(x) x * x   /* НЕВЕРНО */

вызвать square(z+1).

Тем не менее макросы имеют свои достоинства. Один из примеров можно найти в файле <stdio.h>, где getchar и putchar часто реализуют с помощью макросов, чтобы избежать расходов времени на вызов функции для каждого обрабатываемого символа. Функции в <ctype.h> обычно также реализуются с помощью макросов.

Действие #define можно отменить с помощью директивы #undef:

    #undef getchar

    int getchar(void) { ... }

Как правило, это делается, чтобы заменить макроопределение настоящей функцией с тем же именем.

Имена формальных параметров не заменяются, если встречаются в заключенных в кавычки строках. Однако, если в замещающем тексте перед формальным параметром стоит знак #, этот параметр будет заменен на аргумент, заключенный в кавычки. Это можно сочетать с конкатенацией строк, например, чтобы создать макрос отладочного вывода:

    #define dprint(expr) printf(#expr " = %g\n", expr)

Обращение к

    dprint(x/y);

развернется в

    printf("x/y" " = %g\n", x/y);

а в результате конкатенации двух соседних строк, которая будет автоматически выполнена компилятором, получим

    printf("x/y = %g\n", x/y);

Внутри фактического аргумента каждый знак " заменяется на \", а каждая обратная наклонная черта \ на \\, так что в результате подстановки получается правильная символьная константа.

Оператор ## позволяет конкатенировать аргументы в макрорасширениях. Если в замещающем тексте параметр соседствует с ##, то он заменяется соответствующим ему аргументом, а оператор ## и окружающие его символы-разделители выбрасываются. Например, в макропределении paste конкатенируются два аргумента

    #define paste(front, back) front ## back

так что запись paste(name, 1) будет заменена на name1.

Правила вложенных использований оператора ## не определены; другие подробности, относящиеся к ##, можно найти в приложении А.


Упражнение 4-14


Создайте макроопределение swap(t, x, y), которое осуществляет обмен значениями указанного типа t между аргументами x и y. (Примените блочную структуру.)


4.11.3. Условная компиляция

Работой препроцессора можно управлять с помощью условных инструкций. Они представляют собой средство для выборочного включения того или иного текста программы в зависимости от значения условия, вычисляемого во время компиляции.

Вычисляется константное целое выражение, заданное в строке #if. Это выражение не должно содержать операторы sizeof, приведения типов и констант из перечислений enum. Если оно имеет ненулевое значение, то будут включены все последующие строки вплоть до ближайшей директивы #endif, #elif, или #else. (Директива препроцессора #elif действует как else if.) Выражение defined(имя) в #if равно 1, если имя было определено, и 0 в противном случае.

Например, чтобы застраховаться от повторного включения заголовочного файла hdr.h, его можно оформить следующим образом:

    #if !defined(HDR)
    #define HDR

    /* здесь содержимое hdr.h */

    #endif

При первом включении файла hdr.h будет определено имя HDR, а при последующих включениях препроцессор обнаружит, что имя HDR уже определено, и перескочит сразу на #endif. Этот прием может оказаться полезным, когда нужно избежать многократного включения одного и того же файла. Если им пользоваться систематически, то в результате каждый заголовочный файл будет сам включать заголовочные файлы, от которых он зависит, освободив от этого занятия пользователя.

Вот пример цепочки проверок имени SYSTEM, позволяющей выбрать нужный файл для включения:

    #if SYSTEM == SYSV
      #define HDR "sysv.h"
    #elif SYSTEM == BSD
      #define HDR "bsd.h"
    #elif SYSTEM == MSDOS
      #define HDR "msdos.h"
    #else
      #define HDR "default.h"
    #endif
    #include HDR

Инструкции #ifdef и #ifndef специально предназначены для проверки того, определено или нет заданное в них имя. И следовательно, первый пример, приведенный выше для иллюстрации #if, можно записать и в таком виде:

    #ifndef HDR
    #define HDR

    /* здесь содержимое hdr.h */

    #endif

netlib.narod.ru< Назад | Оглавление | Далее >

Сайт управляется системой uCoz