Пособие по практике программирования


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


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

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

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

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

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

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

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

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

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

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

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




- Начало -  - Назад -  - Вперед -