Трюк с #define

09/10/2009 - 23:28

   Один из наиболее частых способов применения директивы #define –  создание макроопределений замаскированных под функции. Для макросов состоящих из одной инструкции, это легко. Просто определяем макрос без  точки с запятой.

#define StartAdc()     ADCSRA |= (1<<ADSC)


InitAdc();         //обычная функция
StartAdc();       //макрос

Но что делать, если макрос состоит из двух и более инструкций?

#define StartAdc()     ADCSRA |= (1<<ADSC); \
                               asm(“sei”)

В общем случае, можно вызывать этот макрос как обычно.

StartAdc();

Препроцессор заменит макроопределение и точка с запятой окажется после второй строчки.

ADCSRA |= (1<<ADSC);
asm(“sei”);

Но что произойдет, если мы используем макрос там, где ожидается только одна инструкция?

if( … )
    StartAdc();

Препроцессор заменит это на

if( … )
   ADCSRA |= (1<<ADSC);
   asm(“sei”);

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

Можно ограничить определение макроса фигурными скобками {}

#define StartAdc()     {ADCSRA |= (1<<ADSC);  asm(“sei”);}


if( … )
    StartAdc();

Препроцессор заменит это на

if( … )
{ADCSRA |= (1<<ADSC); asm(“sei”);};

   Точку с запятой после тела оператора if компилятор проглотит. Никакого криминала здесь нет. Однако этот вариант решения проблемы не будет работать в операторе if…else

   На помощь приходит оператор do{…}while().
Определяем макрос следующим образом:

#define StartAdc()     do{ADCSRA |= (1<<ADSC);  asm(“sei”);}while(0)

И вуаля…

if(…)
    StartAdc();
else
    …

Препроцессор заменит этот макрос и точка с запятой окажется на вполне законном месте – после оператора while.

if(…)
    do{ADCSRA |= (1<<ADSC);  asm(“sei”);}while(0);
else

   Красиво, да?
   Красиво, но зачем нам лишний код, возникнет у кого-то вопрос. Не волнуйтесь. Тело цикла выполнится всего лишь один раз и компилятор, будучи не дураком, не добавит в код ни одной лишней строчки.  
   Впервые я увидел этот трюк в битовых макросах. Помню долго не мог понять, нафига это сделано. Потом мне попался на глаза материал по препроцессору и тогда до меня дошло.
 

P.S.: Более очевидный способ решения подобной проблемы - всегда использовать парные скобки {} для ограничения тела операторов.

#define StartAdc()     ADCSRA |= (1<<ADSC); \
                               asm(“sei”)


if( … ){
    StartAdc();
}

Препроцессор заменит это на

if( … ){
   ADCSRA |= (1<<ADSC);
   asm(“sei”);
}

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

Комментарии   

# alexandershahbazov 08.12.2009 23:09
Я использую парные скобки .
Ответить | Ответить с цитатой | Цитировать
# Guest 05.02.2010 12:48
лучше всегда использовать do{}while(0) - скобки вы банально можете забыть в одном из if - и будете доолго разбираться, почему код работает неправильно. Доолго - потому что этот участок внутри if будет выглядеть совершенно правильно, очевидно. А если часть программы писал другой человек - вообще вилы.
Ответить | Ответить с цитатой | Цитировать
# Guest 14.02.2010 00:19
Парные скобки и не ставить в конце ;

и все. Заодно и макрос от функции будет легко отличить.
Ответить | Ответить с цитатой | Цитировать
# Pashgan 15.02.2010 19:37
Без точки с запятой как-то глаза режет.
Ответить | Ответить с цитатой | Цитировать
# Snake 07.03.2015 18:39
А так не?
Код:#define StartAdc() (ADCSRA |= 1<<ADSC, asm(“sei”))
Ну и что что 5 лет прошло!))
Ответить | Ответить с цитатой | Цитировать
# Guest 30.09.2010 13:37
Ух! спасибо, буду знать!
Ответить | Ответить с цитатой | Цитировать
# Neiver 30.11.2010 15:36
А ещё лучше не извращаться с макросами и использовать встроенные функции (inline). Благо в стандарте C99 они есть и почти все компиляторы его поддерживают.
Ответить | Ответить с цитатой | Цитировать
# Pashgan 30.11.2010 17:22
Ну не знаю, макросы неизбежное зло при программировани и мк.
Ответить | Ответить с цитатой | Цитировать
# Zebra 18.02.2011 16:02
Не согласен.
В 80% случаев с ними надобно бороться.
Ответить | Ответить с цитатой | Цитировать
# YS 24.10.2011 20:47
Прикольный трюк. :D Но на мой взгляд это такой эмбед-цирк. :) Я за inline-функции.
Ответить | Ответить с цитатой | Цитировать
# wukrlvy 29.02.2012 18:21
Макрос #define в программировани и на СИ - это, беспорно, зло. Достаточно заглянуть в один из системных заголовочных файлов, например, iomacro.h, где сосредоточены определения для SFR регистров. Наворотив там комплекс неименованных структур и объединений, фирма IAR достаточно эффективно решила проблему с правильным доступом к битам, одно- и многобайтным SFR регистрам. Но это так далеко "от стандартного СИ"! Зато эффективно.

Создал что-то подобное для нескольких своих внутренних переменных. Хотел иметь словные переменные типа unsigned int с возможностью доступа к отдельным байтам и битам.

В полном соответствии с требованиями языка СИ, декларировал объединение, а в нем структуры. Поскольку декларация объединения общая для нескольких переменных, она должна быть именованной. Также структуры должны быть именованными, иначе транслятор Вас не понимает.

Но теперь в описание моих переменных через точку добавляется имя структуры.
Удалить "лишнее имя" удается только путем декларации макроса, аналогичного макросу для SFR.

Если глянуть на любой из файлов с описанием фирменного драйвера (модуля нижнего уровня) обеспечивающего управление, например, SFR регистрами АЦП, большая часть функций этого уровня описана в виде макросов. Каждая из таких функций после трансляции превращается в одну или несколько строчек ассемблерного кода. Поэтому макросы тут - любимый прием.

И то, что Вы рассмотрели в приведенной выше статье - это только верхушка айсберга под названием #define, на который опирается вся система программировани я для встраиваемых систем.
Ответить | Ответить с цитатой | Цитировать

Добавить комментарий

Защитный код
Обновить