Зачем использовать в макросах явно бессмысленные операторы 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)

Для примера с else я бы добавил выражение типа void в конце ... например, ((недействительно) 0).

Phil1970 08.03.2018 20:59

Напоминаем, что конструкция do while несовместима с операторами return, поэтому конструкция if (1) { ... } else ((void)0) имеет более совместимые применения в стандарте C. А в GNU C вы предпочтете конструкцию, описанную в моем ответе.

Cœur 12.12.2018 14:50
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
827
2
97 032
9
Перейти к ответу Данный вопрос помечен как решенный

Ответы 9

Ответ принят как подходящий

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

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

Если бы вы использовали BAR(X); в операторе if ... else, где тела оператора if не были заключены в фигурные скобки, вы бы получили неприятный сюрприз.

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

Приведенный выше код расширится на

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

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

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

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

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

Вышеупомянутая версия bar BAR расширяет приведенный выше код до следующего, что является синтаксически правильным.

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

Это не сработает, если вместо f(X) у вас есть более сложный код, который должен быть помещен в отдельный блок, например, для объявления локальных переменных. В наиболее общем случае решение состоит в том, чтобы использовать что-то вроде do ... while, чтобы макрос был единственным оператором, который без путаницы принимает точку с запятой.

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

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

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

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

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

В моем собственном ответе пропущена «проблема else, если фигурные скобки и точка с запятой», но вы забыли проблему области видимости, а также оптимизацию компилятора do / while (false) ... :-p ...

paercebal 30.09.2008 22:19

Но почему бы просто не #define FOO (X) {f (X); г (Х); }?

korona 21.11.2008 12:37

@korona: if (выражение) FOO (X); else ... такой код будет недопустимым, потому что у вас есть 2 оператора в предложении if.

baye 14.03.2009 03:21

Разве это не веский аргумент в пользу того, чтобы всегда использовать фигурные скобки в операторах if, while и for? Если вы всегда делаете это (как это требуется, например, для MISRA-C), описанная выше проблема исчезнет.

Steve Melnikoff 03.04.2009 02:51

Пример запятой должен быть #define BAR(X) (f(X), g(X)), иначе приоритет оператора может испортить семантику.

Stewart 19.05.2011 16:58

Разве вы не можете избежать проблемы с болтающимся остальным, используя #define FOO(x) if (1) { ... } else (void)0?

Richard Hansen 18.07.2013 20:56

Как сказал @SteveMelnikoff, каждая упомянутая проблема должна быть решена очень простым исправлением - добавлением скобок в каждую ветвь операторов if / while / for, даже если они содержат только один оператор. Я в основном программист PHP, но всегда везде использую фигурные скобки, даже у меня таких проблем нет :) Использовать скобки везде должно быть проще, чем использовать такие конструкции do/while(0), или я ошибаюсь?

David Ferenczy Rogožan 20.11.2013 18:13

@DawidFerenczy: хотя и ты, и я - четыре с половиной года назад - хорошо замечаем, мы должны жить в реальном мире. Если мы не можем гарантировать, что все операторы if и т. д. В нашем коде используют фигурные скобки, то упаковка макросов, подобная этой, - простой способ избежать проблем.

Steve Melnikoff 20.11.2013 21:16

А что насчет этой простой конструкции: ({})? Смотрите мой ответ.

Cœur 23.03.2014 16:11

Примечание: форма if (1) {...} else void(0) более безопасна, чем do {...} while(0) для макросов, параметры которых представляют собой код, включенный в раскрытие макроса, поскольку она не изменяет поведение ключевых слов break или continue. Например: for (int i = 0; i < max; ++i) { MYMACRO( SomeFunc(i)==true, {break;} ) } вызывает неожиданное поведение, когда MYMACRO определен как #define MYMACRO(X, CODE) do { if (X) { cout << #X << endl; {CODE}; } } while (0), потому что разрыв влияет на цикл while макроса, а не на цикл for в месте вызова макроса.

Chris Kline 22.06.2015 19:23

@ChrisKline void(0) недопустимый код C. Кроме того, он по-прежнему не решает проблему зависания else.

user12205 27.07.2015 13:04

@ace void(0) был опечаткой, я имел в виду (void)0. И я считаю, что этот делает решает проблему "висячего другого": обратите внимание, что после (void)0 нет точки с запятой. В этом случае свисание else (например, if (cond) if (1) foo() else (void)0 else { /* dangling else body */ }) вызывает ошибку компиляции. Вот живой пример, демонстрирующий это

Chris Kline 27.07.2015 14:07

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

Deduplicator 15.03.2018 17:22

@ChrisKline MYMACRO( SomeFunc(i)==true, {break;} ) - плохой код. Попытка решить все возможные проблемы с помощью макросов делает макросы слишком сложными без реальных преимуществ.

12431234123412341234123 31.08.2020 20:13

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

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

if (test) FOO( baz);

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

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

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

"вероятно, непреднамеренно"? Я бы сказал «конечно непреднамеренно», иначе программиста нужно вытащить и расстрелять (а не жестко наказать кнутом).

Lawrence Dol 22.04.2010 06:45

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

R.. GitHub STOP HELPING ICE 26.04.2012 07:09

И этот комментарий просто напоминает мне строку goto fail в недавней ошибке проверки сертификата SSL, обнаруженной в ОС Apple.

Gerard Sexton 11.03.2014 18:31

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

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

Помогите макросу вести себя как настоящий код

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

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

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

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

Итак, у нас должен быть макрос, для которого нужна точка с запятой.

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

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

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, так как тот факт, что его пост-условие ложно, известен во время компиляции. Это означает, что такой макрос, как:

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

Обратите внимание, что изменение макроса на встроенную функцию изменяет некоторые стандартные предопределенные макросы, например следующий код показывает изменение в НАЗНАЧЕНИЕ и ЛИНИЯ: #include <stdio.h> #define Fmacro () printf ("% s% d \ n", НАЗНАЧЕНИЕ, ЛИНИЯ) inline void Finline () {printf ("% s% d \ n ", НАЗНАЧЕНИЕ, ЛИНИЯ); } int main () {Fmacro (); Finline (); возврат 0; } (жирные термины должны быть заключены в двойные подчеркивания - плохое форматирование!)

Gnubie 23.08.2012 14:52

Есть ряд мелких, но не совсем несущественных проблем с этим ответом. Например: void doSomething() { int i = 25 ; { int i = x + 1 ; f(i) ; } ; // was MY_MACRO(32) ; } - неправильное расширение; x в расширении должно быть 32. Более сложный вопрос - что такое расширение MY_MACRO(i+7). И еще одно расширение MY_MACRO(0x07 << 6). Есть много хорошего, но есть некоторые незакрещенные и незакрашенные «i».

Jonathan Leffler 26.08.2013 08:17

@Gnubie: я на случай, если вы все еще здесь, и вы еще не поняли этого: вы можете убрать звездочки и подчеркивания в комментариях с помощью обратной косой черты, поэтому, если вы наберете \_\_LINE\_\_, он отобразится как __LINE__. ИМХО, лучше просто использовать форматирование кода для кода; например, __LINE__ (не требует особого обращения). P.S. Не знаю, было ли это правдой в 2012 году; С тех пор они внесли в двигатель немало улучшений.

Scott 23.08.2017 02:07

Признавая, что мой комментарий опоздал на шесть лет, но большинство компиляторов C на самом деле не встраивают функции inline (как разрешено стандартом)

Andrew 03.01.2019 09:01

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

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

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

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

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

Это не имеет ничего общего с конструкцией do ... while (0).

Trent 18.11.2008 22:54

Правда. Но имеет отношение к теме макросов и функций, а также к тому, как написать макрос, который ведет себя как функция ...

John Nilsson 26.11.2008 22:21

Как и в предыдущем случае, это не ответ, а комментарий. По теме: вот почему вы используете материал только один раз: do { int macroname_i = (i); f(macroname_i); g(macroname_i); } while (/* CONSTCOND */ 0)

mirabilos 21.12.2016 14:49

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

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

Marius 05.06.2011 17:39

@Marius: Ложь; оператор запятой является точкой последовательности и, таким образом, делает гарантирует порядок выполнения. Я подозреваю, что вы перепутали его с запятой в списках аргументов функций.

R.. GitHub STOP HELPING ICE 12.04.2012 09:02

Второе экзотическое предложение сделало мой день лучше.

Spidey 20.06.2013 23:08

Просто хотел добавить, что компиляторы вынуждены сохранять наблюдаемое поведение программы, поэтому оптимизация do / while away не имеет большого значения (при условии, что оптимизация компилятора верна).

Marco A. 31.03.2015 12:42

@MarcoA. хотя вы правы, в прошлом я обнаружил, что оптимизация компилятора, точно сохраняя функцию кода, но изменяя строки, которые, казалось бы, ничего не делали в единственном контексте, нарушает многопоточные алгоритмы. Речь идет о пункте Peterson's Algorithm.

Marius 25.07.2016 15:29

Это также не будет работать для всех видов конструкций, хотя C с тернарным оператором и this довольно выразителен.

mirabilos 21.12.2016 14:45

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

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

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

#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 макроса.

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

Обычно в макросах добавляются символы подчеркивания в имена переменных, например int __i;.

Blaisorblade 22.12.2011 05:03

@Blaisorblade: На самом деле это неверно и незаконно C; ведущие подчеркивания зарезервированы для использования реализацией. Причина, по которой вы видели этот «обычный шаблон», - это чтение системных заголовков («реализация»), которые должны ограничиваться этим зарезервированным пространством имен. Для приложений / библиотек вы должны выбрать свои собственные непонятные, маловероятные имена без подчеркивания, например mylib_internal___i или аналогичный.

R.. GitHub STOP HELPING ICE 12.04.2012 09:05

@R .. Вы правы - я на самом деле читал это в "приложении", ядре Linux, но в любом случае это исключение, поскольку оно не использует стандартную библиотеку (технически, вместо этого, "автономная" реализация C "размещенного").

Blaisorblade 12.04.2012 20:23

@R .. это не совсем правильно: начальные подчеркивания с последующим заглавным или вторым подчеркиванием зарезервированы для реализации во всех контекстах. Начальные подчеркивания, за которыми следует что-то еще, не зарезервированы в локальной области.

Leushenko 03.05.2014 07:15

@Leushenko: Да, но различие достаточно тонкое, и я считаю, что лучше всего сказать людям, чтобы они вообще не использовали такие имена. Люди, которые понимают эту тонкость, вероятно, уже знают, что я замалчиваю детали. :-)

R.. GitHub STOP HELPING ICE 03.05.2014 07:16

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

mirabilos 21.12.2016 14:47

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

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

FOO(1)
printf("abc");

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

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

и будет компилироваться незаметно (хотя некоторые компиляторы могут выдавать предупреждение о недоступности кода). Но оператор printf никогда не будет выполнен.

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

вот почему вы должны сделать #define FOO(X) if (1) { f(X); g(X); } else (void)0

Richard Hansen 03.08.2012 20:41

@RichardHansen: Все еще не так хорошо, потому что, глядя на вызов макроса, вы не знаете, расширяется ли он до оператора или до выражения. Если кто-то предположит позднее, он может написать FOO(1),x++;, что снова даст нам ложное срабатывание. Просто используйте do ... while и все.

Yakov Galka 03.08.2012 21:35

Чтобы избежать недоразумений, достаточно документировать макрос. Я согласен с тем, что do ... while (0) предпочтительнее, но у него есть один недостаток: break или continue будут управлять циклом do ... while (0), а не циклом, содержащим вызов макроса. Так что трюк с if все еще имеет значение.

Richard Hansen 04.08.2012 01:10

Я не понимаю, где вы могли бы разместить break или continue, которые будут видны внутри вашего псевдопетля макроса do {...} while(0). Даже в параметре макроса будет синтаксическая ошибка.

Patrick Schlüter 21.12.2012 20:02

Другой причиной использования конструкции do { ... } while(0) вместо конструкции if whatever является ее идиоматическая природа. Конструкция do {...} while(0) широко распространена, хорошо известна и часто используется многими программистами. Его обоснование и документация известны. Не так для конструкции if. Следовательно, при проверке кода требуется меньше усилий для проверки.

Patrick Schlüter 21.12.2012 20:05

@tristopia: я видел, как люди пишут макросы, которые принимают блоки кода в качестве аргументов (что я не обязательно рекомендую). Например: #define CHECK(call, onerr) if (0 != (call)) { onerr } else (void)0. Его можно использовать как CHECK(system("foo"), break;);, где break; предназначен для обозначения цикла, включающего вызов CHECK().

Richard Hansen 18.07.2013 21:18

Объяснение

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");

использование блокировки блока {} в макросе было бы достаточным для объединения кода макроса, чтобы все выполнялись для одного и того же пути if-condition. do-while around используется для установки точки с запятой в местах, где используется макрос. таким образом, макрос принудительно ведет себя подобным образом. это включает требование наличия конечной точки с запятой при использовании.

Alexander Stohr 21.12.2018 17:38

Библиотека препроцессора 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, посмотрите эту библиотеку.

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

Segmented 26.03.2015 22:20

Обычно вы используете макросы для создания замкнутой среды, то есть вы никогда не используете break (или continue) внутри макроса для управления циклом, который начался / закончился снаружи, это просто плохой стиль и скрывает потенциальные точки выхода.

mirabilos 21.12.2016 14:51

В Boost также есть библиотека препроцессора. Что в этом умопомрачительного?

Rene 01.06.2017 11:45

Риск с else ((void)0) заключается в том, что кто-то может писать YOUR_MACRO(), f();, и он будет синтаксически корректным, но никогда не вызовет f(). С dowhile это синтаксическая ошибка.

melpomene 07.06.2019 23:42

@melpomene так что насчет else do; while (0)?

Carl Lei 15.08.2019 05:29

Другие вопросы по теме