Em muitas macros C / C ++, estou vendo o código da macro envolvido no que parece ser um do while
loop sem sentido . Aqui estão alguns exemplos.
#define FOO(X) do { f(X); g(X); } while (0)
#define FOO(X) if (1) { f(X); g(X); } else
Não consigo ver o que do while
está fazendo. Por que não escrever isso sem ele?
#define FOO(X) f(X); g(X)
c++
c
c-preprocessor
c++-faq
jfm3
fonte
fonte
void
tipo no final ... como ((void) 0) .do while
construção não é compatível com as declarações de retorno; portanto, aif (1) { ... } else ((void)0)
construção tem usos mais compatíveis no Padrão C. E no GNU C, você prefere a construção descrita na minha resposta.Respostas:
O
do ... while
eif ... else
existe para que um ponto-e-vírgula após a macro sempre signifique a mesma coisa. Digamos que você tenha algo parecido com a sua segunda macro.Agora, se você usar
BAR(X);
em umaif ... else
declaração, onde os corpos da declaração if não estiverem entre colchetes, você terá uma surpresa ruim.O código acima seria expandido para
que é sintaticamente incorreto, pois o resto não está mais associado ao if. Não ajuda a agrupar as coisas entre chaves na macro, porque um ponto-e-vírgula após as chaves é sintaticamente incorreto.
Existem duas maneiras de corrigir o problema. O primeiro é usar uma vírgula para sequenciar as instruções dentro da macro sem roubar sua capacidade de agir como uma expressão.
A versão acima da barra
BAR
expande o código acima para o que segue, que é sintaticamente correto.Isso não funciona se, em vez de
f(X)
você possuir um corpo de código mais complicado que precisar ser inserido em seu próprio bloco, digamos, por exemplo, para declarar variáveis locais. No caso mais geral, a solução é usar algo comodo ... while
fazer com que a macro seja uma única declaração que leva um ponto-e-vírgula sem confusão.Você não precisa usá-
do ... while
lo, também pode criar algoif ... else
, embora, quando seif ... else
expanda dentro de umif ... else
, leve a um " dangling else ", o que poderia tornar um problema de dangling else existente ainda mais difícil de encontrar, como no código a seguir .O ponto é usar o ponto e vírgula em contextos em que um ponto e vírgula oscilante é incorreto. Certamente, poderia (e provavelmente deveria) argumentar-se neste ponto que seria melhor declarar
BAR
como uma função real, não como uma macro.Em resumo,
do ... while
existe para solucionar as deficiências do pré-processador C. Quando esses guias de estilo C dizem para você demitir o pré-processador C, esse é o tipo de coisa com a qual eles estão preocupados.fonte
#define BAR(X) (f(X), g(X))
outra maneira a precedência do operador pode atrapalhar a semântica.if
instruções, etc, em nosso código usem chaves, agrupar macros como essa é uma maneira simples de evitar problemas.if(1) {...} else void(0)
formulário é mais seguro que odo {...} while(0)
das macros cujos parâmetros são o código incluído na expansão da macro, porque não altera o comportamento da quebra nem continua as palavras-chave. Por exemplo:for (int i = 0; i < max; ++i) { MYMACRO( SomeFunc(i)==true, {break;} ) }
causa um comportamento inesperado quandoMYMACRO
é definido como#define MYMACRO(X, CODE) do { if (X) { cout << #X << endl; {CODE}; } } while (0)
porque a interrupção afeta o loop while da macro em vez do loop for no site de chamada da macro.void(0)
foi um erro de digitação, eu quis dizer(void)0
. E acredito que isso não resolver o "pendendo mais" problema: aviso não há nenhuma vírgula após o(void)0
. Uma outra oscilação nesse caso (por exemploif (cond) if (1) foo() else (void)0 else { /* dangling else body */ }
) dispara um erro de compilação. Aqui está um exemplo ao vivo demonstrandoMacros são partes de texto copiadas / coladas que o pré-processador colocará no código original; o autor da macro espera que a substituição produza código válido.
Existem três boas "dicas" para ter sucesso nisso:
Ajude a macro a se comportar como código genuíno
O código normal geralmente termina com um ponto e vírgula. O usuário deve visualizar o código sem precisar de um ...
Isso significa que o usuário espera que o compilador produza um erro se o ponto e vírgula estiver ausente.
Mas o verdadeiro motivo realmente bom é que, em algum momento, o autor da macro talvez precise substituir a macro por uma função genuína (talvez embutida). Portanto, a macro deve realmente se comportar como uma.
Portanto, devemos ter uma macro precisando de ponto e vírgula.
Produzir um código válido
Como mostrado na resposta do jfm3, às vezes a macro contém mais de uma instrução. E se a macro for usada dentro de uma instrução if, isso será problemático:
Essa macro pode ser expandida como:
A
g
função será executada independentemente do valor debIsOk
.Isso significa que precisamos adicionar um escopo à macro:
Produzir um código válido 2
Se a macro é algo como:
Podemos ter outro problema no seguinte código:
Porque expandiria como:
Esse código não será compilado, é claro. Então, novamente, a solução está usando um escopo:
O código se comporta corretamente novamente.
Combinando ponto-e-vírgula + efeitos de escopo?
Há um idioma C / C ++ que produz este efeito: O loop do / while:
O do / while pode criar um escopo, encapsulando o código da macro, e precisa de um ponto e vírgula no final, expandindo para o código que precisa de um.
O bônus?
O compilador C ++ otimizará o loop do / while, pois o fato de sua pós-condição ser falsa é conhecido no momento da compilação. Isso significa que uma macro como:
expandirá corretamente como
e é então compilado e otimizado como
fonte
void doSomething() { int i = 25 ; { int i = x + 1 ; f(i) ; } ; // was MY_MACRO(32) ; }
não é a expansão correta; ox
da expansão deve ser 32. Uma questão mais complexa é do que é a expansãoMY_MACRO(i+7)
. E outra é a expansão deMY_MACRO(0x07 << 6)
. Há muito que é bom, mas existem alguns i e não cruzados.\_\_LINE\_\_
ele será renderizado como __LINE__. IMHO, é melhor usar apenas a formatação de código; por exemplo,__LINE__
(que não requer nenhum tratamento especial). PS: Não sei se isso era verdade em 2012; eles fizeram algumas melhorias no mecanismo desde então.inline
funções (conforme permitido pela norma)@ jfm3 - Você tem uma boa resposta para a pergunta. Você também pode adicionar que o idioma da macro também evita o comportamento não intencional possivelmente mais perigoso (porque não há erro) com instruções simples 'se':
expande para:
que é sintaticamente correto, portanto não há erro do compilador, mas tem a conseqüência provavelmente não intencional de que g () sempre será chamado.
fonte
As respostas acima explicam o significado dessas construções, mas há uma diferença significativa entre as duas que não foi mencionada. De fato, há uma razão para preferir
do ... while
oif ... else
construto.O problema da
if ... else
construção é que ela não o força a colocar o ponto e vírgula. Como neste código:Embora tenhamos deixado de fora o ponto-e-vírgula (por engano), o código será expandido para
e será compilado silenciosamente (embora alguns compiladores possam emitir um aviso para código inacessível). Mas a
printf
declaração nunca será executada.do ... while
A construção não tem esse problema, pois o único token válido após owhile(0)
é um ponto e vírgula.fonte
FOO(1),x++;
que novamente nos dará um falso positivo. Basta usardo ... while
e é isso.do ... while (0)
é preferível, mas tem uma desvantagem: Abreak
oucontinue
controlará odo ... while (0)
loop, não o loop que contém a chamada de macro. Portanto, oif
truque ainda tem valor.break
ou umcontinue
que seria visto como dentro do seudo {...} while(0)
pseudo-loop de macros . Mesmo no parâmetro macro, cometeria um erro de sintaxe.do { ... } while(0)
vez deif whatever
construir é a natureza idiomática. Ado {...} while(0)
construção é generalizada, bem conhecida e muito utilizada por muitos programadores. Sua lógica e documentação são facilmente conhecidas. Não é assim para aif
construção. Portanto, é preciso menos esforço para fazer o grok ao fazer a revisão do código.#define CHECK(call, onerr) if (0 != (call)) { onerr } else (void)0
. Pode ser usado comoCHECK(system("foo"), break;);
, onde obreak;
se destina a se referir ao loop que encerra aCHECK()
invocação.Embora seja esperado que os compiladores otimizem os
do { ... } while(false);
loops, existe outra solução que não exigiria essa construção. A solução é usar o operador de vírgula:ou ainda mais exoticamente:
Embora isso funcione bem com instruções separadas, não funcionará com casos em que variáveis são construídas e usadas como parte do
#define
:Com isso, seria forçado a usar a construção do / while.
fonte
Peterson's Algorithm
.A biblioteca de pré-processadores P99 de Jens Gustedt (sim, o fato de que isso existe também me impressionou !) Melhora a
if(1) { ... } else
construção de uma maneira pequena, mas significativa, definindo o seguinte:A lógica para isso é que, diferentemente da
do { ... } while(0)
construção,break
econtinue
ainda funciona dentro do bloco especificado, ele((void)0)
cria um erro de sintaxe se o ponto-e-vírgula for omitido após a chamada de macro, que, caso contrário, ignoraria o próximo bloco. (Na verdade, não há um problema "dangling else" aqui, pois eleelse
se liga ao mais próximoif
, que é o da macro.)Se você estiver interessado nos tipos de coisas que podem ser feitas com mais ou menos segurança com o pré-processador C, consulte essa biblioteca.
fonte
break
(oucontinue
) dentro de uma macro para controlar um loop que começou / terminou fora, isso é apenas um estilo ruim e oculta pontos de saída em potencial.else ((void)0)
é que alguém esteja escrevendoYOUR_MACRO(), f();
e seja sintaticamente válido, mas nunca liguef()
. Comdo
while
isso é um erro de sintaxe.else do; while (0)
?Por algumas razões, não posso comentar a primeira resposta ...
Alguns de vocês mostraram macros com variáveis locais, mas ninguém mencionou que você não pode simplesmente usar qualquer nome em uma macro! Vai morder o usuário algum dia! Por quê? Porque os argumentos de entrada são substituídos no seu modelo de macro. E em seus exemplos de macro, você usa o nome variado provavelmente mais usado, i .
Por exemplo, quando a seguinte macro
é usado na seguinte função
a macro não usará a variável pretendida i, declarada no início de some_func, mas a variável local, declarada no loop do ... while da macro.
Portanto, nunca use nomes de variáveis comuns em uma macro!
fonte
int __i;
.mylib_internal___i
ou similares.Explicação
do {} while (0)
eif (1) {} else
certifique-se de que a macro seja expandida para apenas 1 instrução. De outra forma:expandiria para:
E
g(X)
seria executado fora daif
instrução de controle. Isso é evitado ao usardo {} while (0)
eif (1) {} else
.Melhor alternativa
Com uma expressão de instrução GNU (não faz parte do padrão C), você tem uma maneira melhor do que
do {} while (0)
eif (1) {} else
resolvê-lo, simplesmente usando({})
:E essa sintaxe é compatível com valores de retorno (observe que
do {} while (0)
não é), como em:fonte
Eu não acho que foi mencionado, então considere isso
seria traduzido para
observe como
i++
é avaliado duas vezes pela macro. Isso pode levar a alguns erros interessantes.fonte
do { int macroname_i = (i); f(macroname_i); g(macroname_i); } while (/* CONSTCOND */ 0)