Зачем использовать явно бессмысленные операторы do-while и if-else в макросах?

во многих макросах C / C++ я вижу код макроса, завернутый в то, что кажется бессмысленным do while петли. Вот примеры.

#define FOO(X) do { f(X); g(X); } while (0)
#define FOO(X) if (1) { f(X); g(X); } else

Я не вижу, что do while делает. Почему бы просто не написать это без него?

#define FOO(X) f(X); g(X)

10 ответов


на do ... while и if ... else есть ли сделать так, чтобы точка с запятой после макроса всегда означает одно и то же. Скажем, ты. было что-то вроде твоего второго макроса.

#define BAR(X) f(x); g(x)

макросы-это скопированные / вставленные фрагменты текста, которые предварительный процессор поместит в подлинный код; автор макроса надеется, что замена произведет действительный код.

есть три хороших "советы", чтобы добиться успеха в этом:

помогите макро вести себя как подлинный код

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

doSomething(1) ;
DO_SOMETHING_ELSE(2)  // <== Hey? What's this?
doSomethingElseAgain(3) ;

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

но реальная реальная веская причина заключается в том, что в какое-то время автору макроса, возможно, потребуется заменить макрос подлинной функцией (возможно, встроенной). Таким образом, макрос должен действительно ведут себя как один.

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

введите действительный код

как показано в ответе jfm3, иногда макрос содержит более одной инструкции. И если макрос используется внутри если утверждение, это будет проблематично:

if(bIsOk)
   MY_MACRO(42) ;

этот макрос может быть расширена как:

#define MY_MACRO(x) f(x) ; g(x)

if(bIsOk)
   f(42) ; g(42) ; // was MY_MACRO(42) ;

на g функция будет выполнена независимо от значения bIsOk.

это означает, что мы должны добавить объем в макрос:

#define MY_MACRO(x) { f(x) ; g(x) ; }

if(bIsOk)
   { f(42) ; g(42) ; } ; // was MY_MACRO(42) ;

введите действительный код 2

если макрос что-то вроде:

#define MY_MACRO(x) int i = x + 1 ; f(i) ;

у нас может быть еще одна проблема в следующем код:

void doSomething()
{
    int i = 25 ;
    MY_MACRO(32) ;
}

, потому что он будет расширяться как:

void doSomething()
{
    int i = 25 ;
    int i = 32 + 1 ; f(i) ; ; // was MY_MACRO(32) ;
}

этот код, конечно, не будет компилироваться. Итак, опять же, решение использует область:

#define MY_MACRO(x) { int i = x + 1 ; f(i) ; }

void doSomething()
{
    int i = 25 ;
    { int i = 32 + 1 ; f(i) ; } ; // was MY_MACRO(32) ;
}

код снова ведет себя правильно.

комбинирование эффектов с запятой + объем?

существует одна идиома C / C++, которая производит этот эффект: цикл do/while:

do
{
    // code
}
while(false) ;

do / while может создать область, таким образом инкапсулируя код макроса, и нуждается в в конце концов, полу-двоеточие, таким образом, расширяется в код, нуждающийся в одном.

бонус?

компилятор C++ оптимизирует цикл do/while, так как во время компиляции известно, что его пост-условие false. Это означает, что макрос типа:

#define MY_MACRO(x)                                  \
do                                                   \
{                                                    \
    const int i = x + 1 ;                            \
    f(i) ; g(i) ;                                    \
}                                                    \
while(false)

void doSomething(bool bIsOk)
{
   int i = 25 ;

   if(bIsOk)
      MY_MACRO(42) ;

   // Etc.
}

будет правильно расширяться как

void doSomething(bool bIsOk)
{
   int i = 25 ;

   if(bIsOk)
      do
      {
         const int i = 42 + 1 ; // was MY_MACRO(42) ;
         f(i) ; g(i) ;
      }
      while(false) ;

   // Etc.
}

и затем компилируется и оптимизируется как

void doSomething(bool bIsOk)
{
   int i = 25 ;

   if(bIsOk)
   {
      f(43) ; g(43) ;
   }

   // Etc.
}

@jfm3 - у вас есть хороший ответ на вопрос. Вы также можете добавить, что идиома макроса также предотвращает, возможно, более опасное (потому что нет ошибки) непреднамеренное поведение с помощью простых операторов "if":

#define FOO(x)  f(x); g(x)

if (test) FOO( baz);

расширяется:

if (test) f(baz); g(baz);

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


приведенные выше ответы объясняют смысл этих конструкций, но между ними существует значительная разница, которая не была упомянута. На самом деле, есть причина предпочесть do ... while до if ... else строительство.

проблема if ... else конструкция заключается в том, что она не силу вам поставить точку с запятой. Как в этом коде:

FOO(1)
printf("abc");

хотя мы пропустили точку с запятой (по ошибке), код будет расширяться до

if (1) { f(X); g(X); } else
printf("abc");

и будет молча компилироваться (хотя некоторые компиляторы могут выдавать предупреждение для недостижимого кода). Но ... --5--> инструкция никогда не будет выполнена.

do ... while construct не имеет такой проблемы, так как единственный допустимый токен после while(0) точка с запятой.


хотя ожидается, что компиляторы оптимизируютdo { ... } while(false); петли, есть еще одно решение, которое не потребует этой конструкции. Решение заключается в использовании оператора запятой:

#define FOO(X) (f(X),g(X))

или еще более экзотически:

#define FOO(X) g((f(X),(X)))

хотя это будет хорошо работать с отдельными инструкциями, он не будет работать с случаями, когда переменные построены и используются как часть #define :

#define FOO(X) (int s=5,f((X)+s),g((X)+s))

С этим будет вынужден использовать do / while строить.


Йенс Gustedt это библиотека препроцессоров P99 (да, тот факт, что такая вещь существует взорвал мой мозг тоже!) улучшает if(1) { ... } else построить небольшим, но значительным образом, определив следующее:

#define P99_NOP ((void)0)
#define P99_PREFER(...) if (1) { __VA_ARGS__ } else
#define P99_BLOCK(...) P99_PREFER(__VA_ARGS__) P99_NOP

обоснование этого заключается в том, что, в отличие от do { ... } while(0) строительство, break и continue все еще работают внутри данного блока, но ((void)0) создает синтаксическую ошибку, если точка с запятой опущена после вызова макроса, который в противном случае пропустил бы следующий блок. (На самом деле здесь нет "болтающейся проблемы", так как else привязывается к ближайшему if, который находится в макросе.)

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


по некоторым причинам я не могу прокомментировать первый ответ...

некоторые из вас показали макрос с локальными переменными, но никто не упомянул, что вы не можете просто использовать любое имя в макросе! Он укусит пользователя когда-нибудь! Почему? Поскольку входные аргументы подставляются в шаблон макроса. И в примерах макросов вы используете, вероятно, наиболее часто используемое переменное имя Я.

например, когда следующий макрос

#define FOO(X) do { int i; for (i = 0; i < (X); ++i) do_something(i); } while (0)
это в следующей функции
void some_func(void) {
    int i;
    for (i = 0; i < 10; ++i)
        FOO(i);
}

макрос будет использовать не предполагаемую переменную i, объявленную в начале some_func, а локальную переменную, объявленную в do ... в то время как цикл макроса.

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


Я не думаю, что это было сказано так считают это

while(i<100)
  FOO(i++);

будет переведен на

while(i<100)
  do { f(i++); g(i++); } while (0)

обратите внимание, как i++ дважды оценивается макросом. Это может привести к некоторым интересным ошибки.


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

#define CALL_AND_RETURN(x)  if ( x() == false) break;
do {
     CALL_AND_RETURN(process_first);
     CALL_AND_RETURN(process_second);
     CALL_AND_RETURN(process_third);
     //(simply add other calls here)
} while (0);

объяснение

do {} while (0) и if (1) {} else убедитесь, что макрос расширен только до 1 инструкции. Иначе:

if (something)
  FOO(X); 

расширить:

if (something)
  f(X); g(X); 

и g(X) будет выполнен вне if заявление управления. Это избегается при использовании do {} while (0) и if (1) {} else.


лучше

С GNU выражение (не является частью стандарта C), у вас есть лучше, чем do {} while (0) и if (1) {} else чтобы решить эту проблему, просто используя ({}):

#define FOO(X) ({f(X); g(X);})

и этот синтаксис совместим с возвращаемыми значениями (обратите внимание, что do {} while (0) нет), как в:

return FOO("X");