RSS    

   Реферат: Препроцессор языка C.

и частично - от аргументов. Например,

     #define double(x) (2*(x))

     #define call_with_1(x) x(1)

здесь строка 'call_with_1 (double)' будет заменена на '(2*(1))'.

     Макроопределения не обязательно должны иметь закрывающиеся скобки. Путем

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

макро вызова, начинающегося в теле макроса и заканчивающегося вне его.

Например,

     #define strange(file) fprintf (file, "%s %d",

     ...

     strange(stderr) p, 35)

     В результате обработки этого странного примера получится строка

'fprintf (stderr, "%s %d", p, 35)'.

     4.8.2. Нестандартная группировка арифметических выражений

     Во большинстве примеров макроопределений, рассмотренных выше, каждое

имя макроаргумента заключено в скобки. В дополнение к этому, другая пара

скобок используется для заключения в них всего макроопределения. Далее

описано, почему лучше всего следует писать макросы таким образом.

     Допустим, существует следующее макроопределение:

     #define ceil_div(x, y) (x + y - 1) / y

которое используется для деления с округлением. Затем предположим, что он

используется следующим образом:

     a = ceil_div (b & c, sizeof (int));

     В результате эта строка заменяется на

     a = (b & c + sizeof (int) - 1) / sizeof (int);

которая не выполняет требуемой задачи. Правила приоритета операторов С

позволяют написать следующую сроку:

     a = (b & (c + sizeof (int) - 1)) / sizeof (int);

но требуется

     a = ((b & c) + sizeof (int) - 1)) / sizeof (int);

Если определить макрос следующим образом:

     #define ceil_div(x, y) ((x) + (y) - 1) / (y)

то будет получен желаемый результат.

     Однако, нестандартная группировка может привести к другому результату.

Рассмотрим выражение 'sizeof ceil_div(1, 2)'. Здесь используется выражение

С, вычисляющее размер типа данных 'ceil_div(1, 2)', но в действительности

производятся совсем иные действия. В данном случае указанная срока заменяется

на следующую:

     sizeof ((1) + (2) - 1) / (2)

     Здесь определяется размер типа целого значения и делится пополам.

Правила приоритета помещают операцию деления вне поля действия операции

'sizeof', в то время как должен определяться размер всего выражения.

     Заключение в скобки всего макроопределения позволяет избежать подобных

проблем. Далее дан правильный пример определения макроса 'ceil_div'.

     #define ceil_div(x, y) (((x) + (y) - 1) / (y))

     4.8.3.  Использование точки с запятой

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

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

(аргумент 'p' указывает его местоположение):

     #define SKIP_SPACES (p, limit)  \

     { register char *lim = (limit); \

       while (p != lim) {            \

         if (*p++ != ' ') {          \

           p--; break; }}}

     Здесь последовательность backslash-newline используется для разбиения

макроопределения на несколько строк, поскольку оно должно быть на одной

строке.

     Вызов этого макроса может выглядеть так: 'SKIP_SPACES (p, lim)'. Грубо

говоря, при его вызове он заменяется на составную конструкцию, которая

является полностью законченной и нет необходимости в использовании точки с

запятой для ее завершения. Но вызов этого макроса выглядит как вызов функции.

Поэтому удобнее будет вызывать этот макрос следующим образом:

'SKIP_SPACES (p, lim);'

     Но это может привести к некоторым трудностям при использовании его перед

выражением 'else', так как точка с запятой является пустым выражением.

Рассмотрим такой пример:

     if (*p != 0)

       SKIP_SPACES (p, lim);

     else ...

     Использование двух выражений (составной конструкции и пустого выражения)

между условием 'if' и конструкцией 'else' создает неправильный С код.

     Определение макроса 'SKIP_SPACES' может быть изменено для устранения

этого недостатка с использованием конструкции 'do ... while'.

     #define SKIP_SPACES (p, limit)     \

     do { register char *lim = (limit); \

          while (p != lim) {            \

            if (*p++ != ' ') {          \

              p--; break; }}}           \

     while (0)

     Теперь макрос 'SKIP_SPACES (p, lim);' заменяется на

     do {...} while (0);

что является одним выражением.

     4.8.4.  Удвоение побочных эффектов

     Во многих С программах определяется макрос 'min' для вычисления

минимума:

     #define min(X, Y)  ((X) < (Y) ? (X) : (Y))

     При вызове этого макроса вместе с аргументом, содержащим побочный

эффект, следующим образом:

     next = min (x + y, foo (z));

он заменяется на строку

     next = ((x + y) < (foo (z)) ? (x + y) : (foo (z)));

где значение 'x + y' подставляется вместо 'X', а 'foo (z)' - вместо 'Y'.

     Функция 'foo' используется в этой конструкции только один раз, в то

время как выражение 'foo (z)' используется дважды в макроподстановке. В

результате функция 'foo' может быть вызвана дважды при выполнении выражения.

Если в макросе имеются побочные эффекты или для вычисления значений

аргументов требуется много времени, результат может быть неожиданным. В

данном случае макрос 'min' является ненадежным.

     Наилучшим решением этой проблемы является определение макроса 'min'

таким образом, что значение 'foo (z)' будет вычисляться только один раз. В

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

использованием расширений GNU C это может быть выполнено следующим образом:

     #define min(X, Y)                     \

     ({ typeof (X) __x = (X), __y = (Y);   \

        (__x < __y) ? __x : __y; })

     Если не использовать расширения GNU C, то единственным решением будет

осторожное применение макроса 'min'. Например, для вычисления значения

'foo (z)', можно сохранить его в переменной, а затем использовать ее значение

при вызова макроса:

     #define min(X, Y)  ((X) < (Y) ? (X) : (Y))

     ...

     {

       int tem = foo (z);

       next = min (x + y, tem);

     }

(здесь предполагается, что функция 'foo' возвращает значение типа 'int').

     4.8.5. Рекурсивные макросы

     "Рекурсивные" макросы - это макросы, в определении которых используется

имя самого макроса. Стандарт ANSI C не рассатривает рекурсивный вызов

макроса как вызов. Он поступает на вывод препроцессора без изменений.

     Рассмотрим пример:

     #define foo (4 + foo)

где 'foo' также является переменной в программе.

     Следуя обычным правилам, каждая ссылка на 'foo' заменяется на значение

'(4 + foo)', затем это значение просматривается еще раз и заменяется на

'(4 + (4 + foo))' и так далее, пока это не приведет к ошибке (memory full)

препроцессора.

     Однако, правило об использовании рекурсивных макросов завершит этот

процесс после получения результата '(4 + foo)'. Поэтому этот макрос может

использоваться для прибавления 4 к значению переменной 'foo'.

     В большинстве случаев не следует опираться на эту возможность. При

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

является переменной, а какое - вызовом макроса.

     Также используется специальное правило для "косвенной" рекурсии. Здесь

имеется в виду случай, когда макрос X заменяется на значение 'y', которое

является макросом и заменяется на значение 'x'. В результате ссылка на

макрос 'x' является косвенной и происходит от подстановки макроса 'x', таким

образом, это является рекурсией и далее не обрабатывается. Поэтому после

обработки

     #define x (4 + y)

     #define y (2 * x)

'x' заменяется на '(4 + (2 * x))'.

     Но предположим, что 'y' используется где-либо еще и не в определении

макроса 'x'. Поэтому использование значения 'x' в подстановке макроса 'y'

не является рекурсией. Таким образом, производится подстановка. Однако,

подстановка 'x' содержит ссылку на 'y', а это является косвенной рекурсией.

В результате 'y' заменяется на '(2 * (4 + y))'.

     Неизвестно где такие возможности могут быть использованы, но это

определено стандартом ANSI C.

     4.8.6.  Отдельная подстановка макро аргументов

     Ранее было объяснено, что макроподстановка, включая подставленные

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

вызовов.

     Что же происходит на самом деле, является довольно тонким моментом.

Сначала значения каждого аргумента проверяются на наличие макро вызовов.

Затем полученные значения подставляются в тело макроса и полученная макро

подстановка проверяется еще раз на наличие новых макросов.

     В результате значения макроаргументов проверяются дважды.

     В большинстве случаев это не дает никакого эффекта. Если аргумент

содержит какие-либо макро вызовы, то они обрабатываются при первом проходе.

Полученное значение не содержит макро вызовов и при втором проходе оно не

изменяется. Если же аргументы будут подставлены так, как они были указаны,

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

Страницы: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10


Новости


Быстрый поиск

Группа вКонтакте: новости

Пока нет

Новости в Twitter и Facebook

                   

Новости

© 2010.