Pergunta

Em muitos macros C / C ++ Eu estou vendo o código da macro envolto no que parece ser um loop do while 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 o do while está fazendo. Porque não basta escrever este sem ele?

#define FOO(X) f(X); g(X)
Foi útil?

Solução

O do ... while e if ... else estão lá para fazer com que a ponto e vírgula após a sua macro sempre significa a mesma coisa. Vamos dizer que você tinha algo como sua segunda macro.

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

Agora, se você fosse usar BAR(X); em uma declaração if ... else, onde os corpos da instrução if não foram envoltos em chaves, você terá uma surpresa ruim.

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

O código acima iria expandir-se para

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

que é sintaticamente incorreto, como a outra pessoa não está mais associada com o caso. Não ajuda a coisas envoltório em chaves dentro da macro, porque um ponto e vírgula após as chaves é sintaticamente incorreto.

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

Existem duas maneiras de corrigir o problema. A primeira é usar uma vírgula para declarações sequência dentro do macro sem roubar-lhe a sua capacidade de agir como uma expressão.

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

A versão acima de BAR bar expande o código acima para o que se segue, que é sintaticamente correto.

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

Isto não funciona se em vez de f(X) você tem um corpo mais complicado de código que precisa ir em seu próprio bloco, digamos, por exemplo, para declarar variáveis ??locais. No caso mais geral, a solução é usar algo como do ... while para causar a macro para ser uma única instrução que leva um ponto e vírgula, sem confusão.

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

Você não tem que usar do ... while, você pode inventar algo com if ... else bem, embora quando if ... else expande dentro de um if ... else leva a um " balançando outra ", o que poderia tornar um problema else pendente existente ainda mais difícil de encontrar, como no código a seguir.

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

O ponto é usar o ponto e vírgula em contextos onde um ponto e vírgula pendurado é errônea. Claro, poderia (e provavelmente deve) ser discutido neste momento que seria melhor para BAR declare como uma função real, não uma macro.

Em resumo, o do ... while está lá para trabalho em torno das deficiências do pré-processador C. Quando esses guias de estilo C dizer-lhe para despedir o pré-processador C, este é o tipo de coisa que eles estão preocupados.

Outras dicas

Macros são cópia / peças coladas de texto pré-processador irá colocar no código genuíno; autor do macro espera que a substituição irá produzir um código válido.

Há três boas "dicas" para ter sucesso em que:

Ajude a se comportar macro como o código genuíno

código normal é geralmente terminou com um ponto e vírgula. Caso o código de exibição do usuário não precisar de um ...

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

Isso significa que o utilizador espera que o compilador para produzir um erro se o semi-cólon está ausente.

Mas o verdadeiro motivo real boa é que, em algum momento, o autor do macro será talvez necessário substituir a macro com uma função genuína (talvez embutido). Assim, a macro deve realmente se comportar como um.

Assim, devemos ter uma macro precisando ponto e vírgula.

Produzir um código válido

Como mostrado na resposta das jfm3, por vezes, a macro contém mais de uma instrução. E se a macro é usada dentro de um if, isso vai ser problemático:

if(bIsOk)
   MY_MACRO(42) ;

Esta macro pode ser expandida como:

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

if(bIsOk)
   f(42) ; g(42) ; // was MY_MACRO(42) ;

A função g será executada independentemente do valor de bIsOk.

Isto significa que nós deve ter para adicionar um escopo para a macro:

#define MY_MACRO(x) { f(x) ; g(x) ; }

if(bIsOk)
   { f(42) ; g(42) ; } ; // was MY_MACRO(42) ;

Produzir um código válido 2

Se a macro é algo como:

#define MY_MACRO(x) int i = x + 1 ; f(i) ;

Nós poderíamos ter um outro problema no código a seguir:

void doSomething()
{
    int i = 25 ;
    MY_MACRO(32) ;
}

Porque ele iria expandir como:

void doSomething()
{
    int i = 25 ;
    int i = 32 + 1 ; f(i) ; ; // was MY_MACRO(32) ;
}

Este código não irá compilar, é claro. Então, novamente, a solução é usar um escopo:

#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) ;
}

Os comporta código corretamente novamente.

Combinando-e-vírgula + efeitos de escopo?

Há um C / C ++ idioma que produz este efeito: O fazer / while loop:

do
{
    // code
}
while(false) ;

A fazer / enquanto pode criar um escopo, encapsulando assim o código da macro, e precisa de um ponto e vírgula no final, ampliando em código um precisando.

O bônus?

O compilador C ++ irá otimizar afastado a fazer / while loop, como o fato de sua pós-condição é falsa é conhecido em tempo de compilação. Isto significa que uma macro como:

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

irá expandir corretamente como

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

e depois é compilado e otimizado afastado como

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

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

   // Etc.
}

@ jfm3 - Você tem uma resposta boa para a questão. Você também pode querer acrescentar que o idioma macro também impede a possivelmente mais perigoso (porque não há nenhum erro) comportamento indesejado com simples 'se' declarações:

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

if (test) FOO( baz);

expande para:

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

que é sintaticamente correto por isso não há erro do compilador, mas tem a consequência provavelmente não intencional que g () irá sempre ser chamado.

As respostas acima explicar o significado dessas construções, mas há uma diferença significativa entre os dois que não foi mencionado. Na verdade, há uma razão para preferir o do ... while para a construção if ... else.

O problema da construção if ... else é que ele não força que você colocar o ponto e vírgula. Como neste código:

FOO(1)
printf("abc");

Apesar de termos deixado de fora o ponto e vírgula (por engano), o código irá se expandir para

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

e silenciosamente compilar (embora alguns compiladores podem emitir um aviso de código inacessível). Mas a declaração printf nunca será executado.

construção do ... while não tem tal problema, uma vez que o token só é válida após a while(0) é um ponto e vírgula.

Enquanto espera-se que os compiladores otimizar afastado os loops do { ... } while(false);, não há outra solução que não exigiria que construto. A solução é usar o operador vírgula:

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

ou ainda mais exótica:

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

Enquanto isso vai funcionar bem com instruções em separado, não vai trabalhar com casos em que as variáveis ??são construídos e usados ??como parte do #define:

#define FOO(X) (int s=5,f((X)+s),g((X)+s))

Com este seria forçado a usar a fazer / enquanto construto.

pré-processador biblioteca (sim, o fato de que tal coisa existe explodiu minha ! importa também) melhora na construção if(1) { ... } else em um pequeno, mas significativo maneira, definindo o seguinte:

#define P99_NOP ((void)0)
#define P99_PREFER(...) if (1) { __VA_ARGS__ } else
#define P99_BLOCK(...) P99_PREFER(__VA_ARGS__) P99_NOP

A razão para isso é que, ao contrário da construção do { ... } while(0), break e trabalho continue ainda dentro do bloco dado, mas o ((void)0) cria um erro de sintaxe se o ponto e vírgula é omitida após a chamada macro, que, caso contrário pule o próximo bloco. (Não há realmente um problema "pendendo mais" aqui, já que os vínculos else ao if mais próxima, que é a da macro.)

Se você está interessado no tipo de coisas que podem ser feitas mais ou menos de forma segura com o pré-processador C, veja essa biblioteca.

Por algumas razões eu não posso comentar sobre a primeira resposta ...

Alguns de vocês mostrou macros com variáveis ??locais, mas ninguém mencionou que você não pode apenas usar qualquer nome de uma macro! Ele vai morder o usuário algum dia! Por quê? Porque os argumentos de entrada são substituídos em seu modelo macro. E em seus exemplos de macro que você tem usar o nome variabled provavelmente mais comumente usado i .

Por exemplo, quando a seguinte macro

#define FOO(X) do { int i; for (i = 0; i < (X); ++i) do_something(i); } while (0)

é usado na seguinte função

void some_func(void) {
    int i;
    for (i = 0; i < 10; ++i)
        FOO(i);
}

a macro não irá usar a variável destina i, que é declarada no início do some_func, mas a variável local, que é declarada no fazer ... loop while da macro.

Assim, nunca use nomes de variáveis ??comuns em uma macro!

Explicação

do {} while (0) e if (1) {} else são para se certificar de que a macro é expandida para apenas 1 instrução. Caso contrário:

if (something)
  FOO(X); 

se expandiria para:

if (something)
  f(X); g(X); 

E g(X) seria executado fora da instrução de controle if. Isto é evitado ao utilizar do {} while (0) e if (1) {} else.


Melhor alternativa

Com um GNU expressão declaração (não uma parte do padrão C), você tem uma maneira melhor do que do {} while (0) e if (1) {} else para resolver isso, simplesmente usando ({}):

#define FOO(X) ({f(X); g(X);})

E esta sintaxe é compatível com valores de retorno (note que do {} while (0) não é), como em:

return FOO("X");

Eu não acho que isso foi mencionado por isso considero este

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

seria traduzido em

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

Observe como i++ é avaliada duas vezes pelo macro. Isso pode levar a alguns erros interessantes.

Eu encontrei este truque muito útil é em situações onde você tem que processar sequencialmente um valor particular. Em cada nível de processamento, se algum erro ou condição inválida ocorre, você pode evitar o processamento e sair mais cedo. por exemplo.

#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);
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top