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

<p>во многих макросах c/c++ я вижу код макроса, заключенный в то, что кажется бессмысленным циклом <code>do while</code>. вот примеры.</p><pre><code>#define foo(x) do { f(x); g(x); } while (0) #define foo(x) if (1) { f(x); g(x); } else </code></pre><p>я не вижу, что делает <code>do while</code>. почему бы просто не написать это без этого?</p><pre><code>#define foo(x) f(x); g(x) </code></pre>

<p><code>do ... while</code> и <code>if ... else</code> предназначены для того, чтобы точка с запятой после вашего макроса всегда означала одно и то же. допустим, у вас было что-то вроде вашего второго макроса.</p><pre><code>#define bar(x) f(x); g(x) </code></pre><p>теперь, если бы вы использовали <code>bar(x);</code> в выражении <code>if ... else</code>, где тела оператора if не были заключены в фигурные скобки, вы бы получили неприятный сюрприз.</p><pre><code>if (corge) bar(corge); else gralt(); </code></pre><p>приведенный выше код будет расширяться в</p><pre><code>if (corge) f(corge); g(corge); else gralt(); </code></pre><p>что синтаксически неверно, так как else больше не ассоциируется с if. это не помогает обернуть вещи в фигурные скобки внутри макроса, потому что точка с запятой после скобок синтаксически неверна.</p><pre><code>if (corge) {f(corge); g(corge);}; else gralt(); </code></pre><p>есть два способа решения проблемы. во-первых, использовать запятую для упорядочения операторов внутри макроса, не лишая его способности действовать как выражение.</p><pre><code>#define bar(x) f(x), g(x) </code></pre><p>приведенная выше версия bar <code>bar</code> расширяет приведенный выше код следующим образом, что является синтаксически правильным.</p><pre><code>if (corge) f(corge), g(corge); else gralt(); </code></pre><p>это не работает, если вместо <code>f(x)</code> у вас есть более сложное тело кода, которое должно идти в своем собственном блоке, например, для объявления локальных переменных. в самом общем случае решение состоит в том, чтобы использовать что-то вроде <code>do ... while</code>, чтобы макрос был единственным оператором, который без запятых принимает точку с запятой.</p><pre><code>#define bar(x) do { \ int i = f(x); \ if (i > 4) g(i); \ } while (0) </code></pre><p>вам не нужно использовать <code>do ... while</code>, вы также можете приготовить что-нибудь с помощью <code>if ... else</code>, хотя когда <code>if ... else</code> раскрывается внутри <code>if ... else</code>, это приводит к " <a href="http://en.wikipedia.org/wiki/dangling\_else">dangling else</a> ", что может сделать существующую проблему висячего еще труднее найти, как в следующем коде.</p><pre><code>if (corge) if (1) { f(corge); g(corge); } else; else gralt(); </code></pre><p>смысл в том, чтобы использовать точку с запятой в тех случаях, когда висячая точка с запятой ошибочна. конечно, на этом этапе можно (и, вероятно, следует) утверждать, что было бы лучше объявить <code>bar</code> как реальную функцию, а не как макрос.</p><p>таким образом, <code>do ... while</code> позволяет обойти недостатки препроцессора c. когда эти руководства по стилю с говорят вам уволить препроцессор с, это то, о чем они беспокоятся.</p>

<p>макросы - это скопированные/вставленные фрагменты текста, которые препроцессор вставит в подлинный код; автор макроса надеется, что замена даст действительный код.</p><p>в этом есть три хороших «совета»:</p><h2>помогите макросу вести себя как настоящий код</h2><p>обычный код обычно заканчивается точкой с запятой. если пользователь просматривает код, который ему не нужен ...</p><pre><code>dosomething(1) ; do\_something\_else(2) // <== hey? what's this? dosomethingelseagain(3) ; </code></pre><p>это означает, что пользователь ожидает, что компилятор выдаст ошибку, если точка с запятой отсутствует.</p><p>но действительно веская причина в том, что когда-нибудь автору макроса, возможно, потребуется заменить макрос подлинной функцией (возможно, встроенной). таким образом, макрос должен<strong> действительно </strong>вести себя как единое целое.</p><p>таким образом, у нас должен быть макрос, которому нужна точка с запятой.</p><h2>создайте действительный код</h2><p>как показано в ответе jfm3, иногда макрос содержит более одной инструкции. и если макрос используется внутри оператора if, это будет проблематично:</p><pre><code>if(bisok) my\_macro(42) ; </code></pre><p>этот макрос может быть расширен как:</p><pre><code>#define my\_macro(x) f(x) ; g(x) if(bisok) f(42) ; g(42) ; // was my\_macro(42) ; </code></pre><p>функция <code>g</code> будет выполняться независимо от значения <code>bisok</code>.</p><p>это означает, что мы должны добавить область действия к макросу:</p><pre><code>#define my\_macro(x) { f(x) ; g(x) ; } if(bisok) { f(42) ; g(42) ; } ; // was my\_macro(42) ; </code></pre><h2>введите действительный код 2</h2><p>если макрос что-то вроде:</p><pre><code>#define my\_macro(x) int i = x + 1 ; f(i) ; </code></pre><p>у нас может быть другая проблема в следующем коде:</p><pre><code>void dosomething() { int i = 25 ; my\_macro(32) ; } </code></pre><p>потому что это будет расширяться как:</p><pre><code>void dosomething() { int i = 25 ; int i = 32 + 1 ; f(i) ; ; // was my\_macro(32) ; } </code></pre><p>этот код не будет компилироваться, конечно. итак, опять же, решение использует область:</p><pre><code>#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) ; } </code></pre><p>код снова ведет себя правильно.</p><h2>комбинируете точку с запятой + эффекты эффектов?</h2><p>существует одна идиома c/c++, которая производит этот эффект: цикл do/while:</p><pre><code>do { // code } while(false) ; </code></pre><p>do/while может создать область видимости, таким образом инкапсулируя код макроса, и, в конце концов, нуждается в точке с запятой, расширяя ее до кода, нуждающегося в нем.</p><p>бонус?</p><p>компилятор c++ оптимизирует цикл do/while, так как факт его пост-условия ложен, известен во время компиляции. это означает, что макрос похож на:</p><pre><code>#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. } </code></pre><p>будет правильно расширяться как</p><pre><code>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. } </code></pre><p>а затем компилируется и оптимизируется как</p><pre><code>void dosomething(bool bisok) { int i = 25 ; if(bisok) { f(43) ; g(43) ; } // etc. } </code></pre>

<p>@ jfm3 - у тебя хороший ответ на вопрос. вы также можете добавить, что идиома макроса также предотвращает, возможно, более опасное (так как ошибки нет) непреднамеренное поведение с помощью простых операторов if:</p><pre><code>#define foo(x) f(x); g(x) if (test) foo( baz); </code></pre><p>расширяется до:</p><pre><code>if (test) f(baz); g(baz); </code></pre><p>что синтаксически правильно, поэтому нет ошибки компилятора, но имеет, вероятно, непреднамеренное следствие, что всегда будет вызываться g().</p>

<p>приведенные выше ответы объясняют значение этих конструкций, но между этими двумя понятиями нет существенного различия. на самом деле, есть причина предпочитать <code>do ... while</code> конструкции <code>if ... else</code>.</p><p>проблема конструкции <code>if ... else</code> состоит в том, что она не ставит точку с запятой <em>force</em>. как в этом коде:</p><pre><code>foo(1) printf("abc"); </code></pre><p>хотя мы пропустили точку с запятой (по ошибке), код расширится до</p><pre><code>if (1) { f(x); g(x); } else printf("abc"); </code></pre><p>и будет автоматически компилироваться (хотя некоторые компиляторы могут выдавать предупреждение о недоступном коде). но оператор <code>printf</code> никогда не будет выполнен.</p><p>конструкция <code>do ... while</code> не имеет такой проблемы, поскольку единственный действительный токен после <code>while(0)</code> - точка с запятой.</p>

<p>хотя ожидается, что компиляторы оптимизируют циклы <code>do { ... } while(false);</code>, существует другое решение, которое не требует такой конструкции. решением является использование оператора запятой:</p><pre><code>#define foo(x) (f(x),g(x)) </code></pre><p>или даже более экзотично:</p><pre><code>#define foo(x) g((f(x),(x))) </code></pre><p>хотя это будет хорошо работать с отдельными инструкциями, оно не будет работать в тех случаях, когда переменные создаются и используются как часть <code>#define</code>:</p><pre><code>#define foo(x) (int s=5,f((x)+s),g((x)+s)) </code></pre><p>при этом один будет вынужден использовать конструкцию do/while.</p>

<p> <a href="http://p99.gforge.inria.fr/p99-html/index.html">библиотека препроцессора jens gustedt</a> (да, тот факт, что такая вещь существует, поразила и меня!) улучшает конструкцию <code>if(1) { ... } else</code> небольшим, но значительным образом, определяя следующее:</p><pre class="lang-c prettyprint-override"><code>#define p99\_nop ((void)0) #define p99\_prefer(...) if (1) { \_\_va\_args\_\_ } else #define p99\_block(...) p99\_prefer(\_\_va\_args\_\_) p99\_nop </code></pre><p>основанием для этого является то, что, в отличие от конструкции <code>do { ... } while(0)</code>, <code>break</code> и <code>continue</code> по-прежнему работают внутри данного блока, но <code>((void)0)</code> создает синтаксическую ошибку, если точка с запятой пропускается после вызова макроса, что в противном случае пропустило бы следующий блок. (на самом деле здесь нет проблемы «висячего другого», поскольку <code>else</code> привязывается к ближайшему <code>if</code>, который есть в макросе.) </p><p>если вы заинтересованы в том, что можно сделать более или менее безопасно с помощью препроцессора c, посмотрите эту библиотеку.</p>

<p>по некоторым причинам я не могу прокомментировать первый ответ ...</p><p>некоторые из вас показали макросы с локальными переменными, но никто не упомянул, что вы не можете просто использовать любое имя в макросе! это когда-нибудь укусит пользователя! зачем? потому что входные аргументы подставляются в ваш шаблон макроса. и в ваших примерах макросов вы используете, вероятно, наиболее часто используемое имя переменной<strong> i </strong>.</p><p>например, когда следующий макрос</p><pre><code>#define foo(x) do { int i; for (i = 0; i < (x); ++i) do\_something(i); } while (0) </code></pre><p>используется в следующей функции</p><pre><code>void some\_func(void) { int i; for (i = 0; i < 10; ++i) foo(i); } </code></pre><p>макрос будет использовать не предполагаемую переменную i, объявленную в начале some\_func, а локальную переменную, объявленную в цикле do ... while макроса.</p><p>таким образом, никогда не используйте общие имена переменных в макросе!</p>

<p>я не думаю, что это было упомянуто, так что подумайте</p><pre><code>while(i<100) foo(i++); </code></pre><p>будет переведен на</p><pre><code>while(i<100) do { f(i++); g(i++); } while (0) </code></pre><p>обратите внимание, как макрос <code>i++</code> оценивается дважды. это может привести к некоторым интересным ошибкам.</p>

<p>я нашел этот трюк очень полезным в ситуациях, когда вам нужно последовательно обрабатывать определенное значение. на каждом уровне обработки, если возникает какая-либо ошибка или недопустимое условие, вы можете избежать дальнейшей обработки и выйти из строя рано. например.</p><pre><code>#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); </code></pre>

<h3>объяснение</h3><p><code>do {} while (0)</code> и <code>if (1) {} else</code> предназначены для того, чтобы макрос был расширен только до 1 инструкции. иначе:</p><pre><code>if (something) foo(x); </code></pre><p>будет расширяться до:</p><pre><code>if (something) f(x); g(x); </code></pre><p>и <code>g(x)</code> будет выполняться вне управляющего оператора <code>if</code>. этого избегают при использовании <code>do {} while (0)</code> и <code>if (1) {} else</code>.</p><hr><h3>лучшая альтернатива</h3><p>с выражением оператора gnu <a href="https://gcc.gnu.org/onlinedocs/gcc-6.2.0/gcc/statement-exprs.html#statement-exprs"></a> (не являющимся частью стандартного c) у вас есть лучший способ решить эту проблему, чем <code>do {} while (0)</code> и <code>if (1) {} else</code>, просто используя <code>({})</code>:</p><pre><code>#define foo(x) ({f(x); g(x);}) </code></pre><p>и этот синтаксис совместим с возвращаемыми значениями (обратите внимание, что <code>do {} while (0)</code> нет), как в:</p><pre><code>return foo("x"); </code></pre>

<p>причина, по которой <code>do {} while (0)</code> используется вместо <code>if (1) {}</code>, заключается в том, что никто не может изменить код перед вызовом макроса <code>do {} while (0)</code>, чтобы он был каким-то другим типом блока. например, если вы вызвали макрос, окруженный <code>if (1) {}</code>, например:</p><pre><code>else macro(x); </code></pre><p>это на самом деле <code>else if</code>. тонкая разница</p>