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

Не уверен насчет Oracle или sql-server, но в MySQL вы можете просто использовать *, как обычно.

mysql> select count(id), count(id)*10 from tablename;
+-----------+--------------+
| count(id) | count(id)*10 |
+-----------+--------------+
|       961 |         9610 |
+-----------+--------------+
1 row in set (0.00 sec)
729
задан Lundin 22 May 2017 в 09:29
поделиться

5 ответов

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

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

Теперь, если необходимо было использовать BAR(X); в if ... else оператор, где тела, если бы оператор не был обернут в фигурные скобки, Вы получили бы плохое удивление.

if (corge)
  BAR(corge);
else
  gralt();

вышеупомянутый код расширился бы в [1 124]

if (corge)
  f(corge); g(corge);
else
  gralt();

, который является синтаксически неправильным, поскольку еще больше не связывается с если. Это не помогает обернуть вещи в фигурные скобки в рамках макроса, потому что точка с запятой после фигурных скобок является синтаксически неправильной.

if (corge)
  {f(corge); g(corge);};
else
  gralt();

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

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

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

if (corge)
  f(corge), g(corge);
else
  gralt();

Это не работает, если вместо [1 113] у Вас более сложное тело кода, который должен войти в его собственный блок, сказать, например, для объявления локальных переменных. В наиболее общем случае решение состоит в том, чтобы использовать что-то как [1 114], чтобы заставить макрос быть отдельным оператором, который берет точку с запятой без беспорядка.

#define BAR(X) do { \
  int i = f(X); \
  if (i > 4) g(i); \
} while (0)

Вы не должны использовать do ... while, Вы могли приготовить что-то с [1 116] также, хотя, когда if ... else расширяется в if ... else, это еще приводит к" свисание ", который еще мог сделать существующее свисание проблемой еще тяжелее для нахождения, как в следующем коде.

if (corge)
  if (1) { f(corge); g(corge); } else;
else
  gralt();

точка должна израсходовать точку с запятой в контекстах, где повисшая точка с запятой ошибочна. Конечно, это могло (и вероятно должен), будьте обсуждены в этой точке, которую было бы лучше объявить BAR как фактическая функция, не макрос.

, Таким образом, эти do ... while там для работы вокруг недостатков препроцессора C. Когда те руководства по стилю C говорят Вам откладывать препроцессор C, это - вид вещи, по поводу которой они волнуются.

776
ответ дан Pavel 22 May 2017 в 09:29
поделиться

Макросы, копируют/вставляют части текста, препроцессор вставит подлинный код; автор макроса надеется, что замена произведет допустимый код.

существует три хороших "подсказки" для преуспевания в этом:

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

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

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 функция будет выполняться независимо от значения [1 113].

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

#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
{
    // code
}
while(false) ;

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

премия?

компилятор C++ оптимизирует далеко/цикл с условием продолжения, как факт его постусловие является ложью, известен во время компиляции. Это означает что макрос как:

#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.
}

расширится правильно как [1 137]

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.
}

и тогда скомпилирован и оптимизирован далеко как [1 138]

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

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

   // Etc.
}
144
ответ дан Ville-Valtteri 22 May 2017 в 09:29
поделиться

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

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

if (test) FOO( baz);

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

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

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

51
ответ дан Michael Burr 22 May 2017 в 09:29
поделиться

Я не думаю, что это было упомянуто, так полагайте, что это

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

было бы переведено в

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

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

2
ответ дан John Nilsson 22 May 2017 в 09:29
поделиться

Хотя ожидается, что компиляторы оптимизируют циклы 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.

16
ответ дан 22 November 2019 в 21:26
поделиться