Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
kernigan_paik.doc
Скачиваний:
0
Добавлен:
01.07.2025
Размер:
2.91 Mб
Скачать

1.4. Макрофункции

В среде программистов, давно пишущих на С, существует тенденция писать макросы вместо функций для очень коротких вычислений, кото­рые будут часто вызываться: операции ввода-вывода, такие как getcha r, и проверки символов вроде isdigit — это, так сказать, официально утвержденные примеры. Причина — в производительности: у макросов нет "накладных расходов", которые свойственны вызовам функций.

На самом деле этот аргумент был не слишком убедительным уже в те времена, когда С только появился, — в эпоху медленных машин и "дорогих" вызовов функций; теперь же он просто нелеп. Для современных машин и компиляторов недостатки макрофункций перевешивают их достоинства.

Избегайте макрофункций. В C++ встраиваемые (inline) функции дела­ют использование макрофункций ненужным; в Java макросов вообще не существует. В С они больше проблем создают, чем решают.

Одна из наиболее серьезных проблем, связанных с макрофункциями: | параметр, который появляется в определении более одного раза, может' быть вычислен также более одного раза; если же аргумент вызова вклю­чает в себя выражение с побочными эффектами, то результатом будет трудно отлавливаемая ошибка. В приведенном коде сделана попытка самостоятельно реализовать одну из проверок символов из <ctype. h>:

? #define isupper(c) ((с) >= 'A' && (с) <= 'Z')

Обратите внимание на то, что параметр с дважды появляется в теле мак-" роса. Если же наш макрос isupper вызывается в контексте вроде такого:

? while (isupper(c = getchar()))

? ..................

то каждый раз, когда вводимый символ будет больше или равен А, он бу­дет пропущен, и для сравнения с Z будет считан еще один символ. Стан­дарт С написан таким образом, чтобы функции, аналогичные isupper, можно было реализовать как макросы, но только если они гарантируют, что аргумент будет вычисляться лишь единожды, так что приведенная выше реализация не отвечает требованиям стандарта.

Всегда лучше использовать уже имеющиеся функции ctype, чем реали­зовывать их самостоятельно; кроме того, функции с побочными эффекта­ми, типа getcha г, лучше не применять внутри составных выражений. Если разбить условие цикла на два выражения, то оно будет более понятно для читателя и, кроме того, даст возможность четко отследить конец файла:

while ((с = getchar()) != EOF && isupper(c))

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

? #define ROUND _TO_INT(x) ((int) ((x) + (((x)>0)?0.5:-0. 5)))

? .........

? size= ROUND _TO_INT(sqrt(dx*dx + dy*dy));

Вычисление квадратного корня будет производиться в два раза чаще, чем требуется (он передается как аргумент, который дважды участвует в вы­числении). Даже при задании простого аргумента сложное выражение вроде ROUND_TO_INT преобразуется во множество машинных команд, кото­рые лучше хранить в одной функции, вызываемой при необходимости. Обращения к макросу увеличивают размер скомпилированной програм­мы. (Встраиваемые (inline) функции в C++ имеют тот же недостаток.)

Заключайте тело макроса и аргументы в скобки. Если вы все же реши­ли использовать макрофункции, будьте с ними осторожны. Макрос ра­ботает за счет простой подстановки текста: параметры в описании заме­няются аргументами вызова, и результат (текст!) замещает собой текст вызова. В этом состоит важное отличие макросов от функций, делающее макросы столь ненадежными. Так, выражение

1 / square(x)

будет работать отлично, если square — это функция, однако если это макрос вроде следующего:

? #define square(x) (x) * (х)

то выражение будет преобразовано в ошибочное:

? 1 / (x)*(x)

Этот макрос надо периписать так:

#define square(x) ((x) * (х))

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

В C++ встраиваемые (inline) функции позволяют избежать синтакси­ческих проблем, сохраняя при этом высокую производительность, при­сущую макросам. Они хорошо подходят для коротких функций, кото­рые получают или устанавливают только одно значение.

Упражнение 1-9

Определите все проблемы, связанные с приведенным описанием макроса:

? #define ISDIGIT(c) ((с >= '0') && (с <= '9')) ? 1 : 0

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]