Во многих макросах 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)
c++
c
c-preprocessor
c++-faq
jfm3
источник
источник
void
типа в конце ... like ((void) 0) .do while
конструкция несовместима с операторами return, поэтомуif (1) { ... } else ((void)0)
конструкция имеет более совместимые применения в стандарте C. А в GNU C вы предпочтете конструкцию, описанную в моем ответе.Ответы:
do ... while
Иif ... else
там , чтобы сделать это так , что точка с запятой после макроса всегда означает то же самое. Допустим, у вас было что-то вроде вашего второго макроса.Теперь, если бы вы использовали
BAR(X);
вif ... else
утверждении, где тела оператора if не были заключены в фигурные скобки, вы бы получили неприятный сюрприз.Приведенный выше код будет расширяться в
что синтаксически неверно, так как else больше не ассоциируется с if. Это не помогает обернуть вещи в фигурные скобки внутри макроса, потому что точка с запятой после скобок синтаксически неверна.
Есть два способа решения проблемы. Во-первых, использовать запятую для упорядочения операторов внутри макроса, не лишая его способности действовать как выражение.
Приведенная выше версия bar
BAR
расширяет приведенный выше код до следующего, что является синтаксически правильным.Это не работает, если вместо
f(X)
вас есть более сложное тело кода, которое должно идти в своем собственном блоке, например, для объявления локальных переменных. В самом общем случае решение состоит в том, чтобы использовать что-то вроде того,do ... while
чтобы макрос был единственным оператором, который использует точку с запятой без путаницы.Вам не нужно использовать
do ... while
, вы также можете приготовить что-нибудьif ... else
, хотя, когда выif ... else
расширяетесь внутри,if ... else
это приводит к « висячему другому », что может сделать существующую проблему висящего другого еще труднее найти, как в следующем коде ,Смысл в том, чтобы использовать точку с запятой в тех случаях, когда висячая точка с запятой ошибочна. Конечно, на этом этапе можно (и, вероятно, следует) утверждать, что было бы лучше объявить
BAR
реальную функцию, а не макрос.Таким образом,
do ... while
можно обойти недостатки препроцессора C. Когда эти руководства по стилю С говорят вам уволить препроцессор С, это то, о чем они беспокоятся.источник
#define BAR(X) (f(X), g(X))
противном случае приоритет оператора может испортить семантику.if
операторы и т. Д. В нашем коде используют фигурные скобки, то перенос таких макросов является простым способом избежать проблем.if(1) {...} else void(0)
Форма безопаснееdo {...} while(0)
макросов for, параметрами которых является код, включенный в расширение макроса, поскольку он не изменяет поведение ключевых слов 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 на сайте вызова макроса.void(0)
был опечаткой, я имел в виду(void)0
. И я считаю , что это действительно решить «оборванных» еще проблема: извещение нет никакого точка с запятой после(void)0
. В этом случае висящий еще (напримерif (cond) if (1) foo() else (void)0 else { /* dangling else body */ }
) вызывает ошибку компиляции. Вот живой пример, демонстрирующий этоМакросы - это скопированные / вставленные фрагменты текста, которые препроцессор вставит в подлинный код; Автор макроса надеется, что замена даст действительный код.
В этом есть три хороших «совета»:
Помогите макросу вести себя как подлинный код
Обычный код обычно заканчивается точкой с запятой. Если пользователь просматривает код, который ему не нужен ...
Это означает, что пользователь ожидает, что компилятор выдаст ошибку, если точка с запятой отсутствует.
Но по-настоящему веская причина в том, что когда-нибудь автору макроса, возможно, потребуется заменить макрос подлинной функцией (возможно, встроенной). Таким образом, макрос должен действительно вести себя как один.
Таким образом, у нас должен быть макрос, которому нужна точка с запятой.
Создайте действительный код
Как показано в ответе jfm3, иногда макрос содержит более одной инструкции. И если макрос используется внутри оператора if, это будет проблематично:
Этот макрос может быть расширен как:
g
Функция будет выполнена независимо от значенияbIsOk
.Это означает, что мы должны добавить область действия в макрос:
Введите действительный код 2
Если макрос что-то вроде:
У нас может быть другая проблема в следующем коде:
Потому что это будет расширяться как:
Этот код не будет компилироваться, конечно. Итак, опять же, решение использует область:
Код снова ведет себя правильно.
Сочетание точек с запятой + эффектов
Существует одна идиома C / C ++, которая производит такой эффект: цикл do / while:
Do / while может создать область видимости, таким образом инкапсулируя код макроса, и, в конце концов, нуждается в точке с запятой, расширяя ее до кода, нуждающегося в нем.
Бонус?
Компилятор C ++ оптимизирует цикл do / while, так как факт его пост-условия ложен, известен во время компиляции. Это означает, что макрос, как:
будет правильно расширяться как
а затем компилируется и оптимизируется как
источник
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)
. Есть много хороших вещей, но есть несколько не пунктирных я и непересекающихся.\_\_LINE\_\_
наберете, он будет отображаться как __LINE__ ИМХО, лучше просто использовать форматирование кода для кода; например,__LINE__
(который не требует специальной обработки). PS Я не знаю, было ли это правдой в 2012 году; с тех пор они внесли несколько улучшений в двигатель.inline
функции (как это разрешено стандартом)@ jfm3 - У вас есть хороший ответ на вопрос. Вы также можете добавить, что идиома макроса также предотвращает, возможно, более опасное (из-за отсутствия ошибок) непреднамеренное поведение с помощью простых операторов if:
расширяется до:
что синтаксически правильно, так что нет ошибки компилятора, но имеет, вероятно, непреднамеренное следствие, что g () всегда будет вызываться.
источник
Приведенные выше ответы объясняют значение этих конструкций, но между этими двумя понятиями нет существенного различия. На самом деле, есть причина предпочесть
do ... while
вif ... else
конструкции.Проблема
if ... else
конструкции в том, что она не заставляет вас ставить точку с запятой. Как в этом коде:Хотя мы пропустили точку с запятой (по ошибке), код расширится до
и будет автоматически компилироваться (хотя некоторые компиляторы могут выдавать предупреждение о недоступном коде). Но
printf
заявление никогда не будет выполнено.do ... while
У construct такой проблемы нет, так как единственный действительный токен после точкиwhile(0)
с запятой.источник
FOO(1),x++;
что снова даст нам ложный позитив. Просто используйтеdo ... while
и все.do ... while (0)
это предпочтительнее, но у него есть один недостаток: Abreak
илиcontinue
будет контролироватьdo ... while (0)
цикл, а не цикл, содержащий вызов макроса. Таким образом,if
уловка все еще имеет значение.break
или a,continue
которые были бы видны как внутри вашего макросаdo {...} while(0)
псевдо-цикла. Даже в параметре макроса это может привести к синтаксической ошибке.do { ... } while(0)
вместоif whatever
конструкции, это идиоматическая природа этого.do {...} while(0)
Конструкция широко, хорошо известна и используется много многими программистами. Его обоснование и документация хорошо известны. Не так дляif
конструкции. Поэтому требуется меньше усилий, чтобы выполнить анализ кода.#define CHECK(call, onerr) if (0 != (call)) { onerr } else (void)0
. Это может быть использовано, напримерCHECK(system("foo"), break;);
, гдеbreak;
предназначен для ссылки на цикл, включающийCHECK()
вызов.Хотя ожидается, что компиляторы оптимизируют
do { ... } while(false);
циклы, существует другое решение, которое не требует такой конструкции. Решением является использование оператора запятой:или даже более экзотично:
Хотя это будет хорошо работать с отдельными инструкциями, оно не будет работать в тех случаях, когда переменные создаются и используются как часть
#define
:При этом один будет вынужден использовать конструкцию do / while.
источник
Peterson's Algorithm
.Библиотека препроцессора Jens Gustedt P99 (да, тот факт, что такая вещь существует, поразила и меня!) Улучшает
if(1) { ... } else
конструкцию небольшим, но значительным образом, определяя следующее:Основанием для этого является то , что, в отличие от
do { ... } while(0)
конструкции,break
и по-continue
прежнему работать внутри данного блока, но((void)0)
создает синтаксическую ошибку , если точка с запятой опущена после вызова макроса, который в противном случае пропустить следующий блок. (На самом деле здесь нет проблемы "зависания", посколькуelse
связывается с ближайшимif
, который находится в макросе.)Если вы заинтересованы в том, что можно сделать более или менее безопасно с препроцессором C, посмотрите эту библиотеку.
источник
break
(илиcontinue
) внутри макроса для управления циклом, который начинается / заканчивается снаружи, это просто плохой стиль и скрывает потенциальные точки выхода.else ((void)0)
состоит в том, что кто-то может писать,YOUR_MACRO(), f();
и это будет синтаксически допустимым, но никогда не будет звонитьf()
. Сdo
while
синтаксической ошибкой.else do; while (0)
?По некоторым причинам я не могу прокомментировать первый ответ ...
Некоторые из вас показали макросы с локальными переменными, но никто не упомянул, что вы не можете просто использовать любое имя в макросе! Это когда-нибудь укусит пользователя! Почему? Потому что входные аргументы подставляются в ваш шаблон макроса. И в ваших примерах макросов вы используете, вероятно, наиболее часто используемое имя переменной i .
Например, когда следующий макрос
используется в следующей функции
макрос будет использовать не предполагаемую переменную i, объявленную в начале some_func, а локальную переменную, объявленную в цикле do ... while макроса.
Таким образом, никогда не используйте общие имена переменных в макросе!
источник
int __i;
.mylib_internal___i
или аналогичные.объяснение
do {} while (0)
иif (1) {} else
убедитесь, что макрос расширен только до 1 инструкции. В противном случае:будет расширяться до:
И
g(X)
будет выполнено внеif
контрольного оператора. Этого избегают при использованииdo {} while (0)
иif (1) {} else
.Лучшая альтернатива
С выражением оператора GNU (не являющимся частью стандартного C) у вас есть лучший способ, чем
do {} while (0)
иif (1) {} else
решить это, просто используя({})
:И этот синтаксис совместим с возвращаемыми значениями (обратите внимание, что
do {} while (0)
это не так), как в:источник
Я не думаю, что это было упомянуто, так что подумайте об этом
будет переведен на
обратите внимание, как
i++
макрос оценивается дважды. Это может привести к некоторым интересным ошибкам.источник
do { int macroname_i = (i); f(macroname_i); g(macroname_i); } while (/* CONSTCOND */ 0)