While(0), Por que Macros o Usam?

A construção do{…}while(0) é usada para permitir que macros se comportem como funções. É muito fácil encontrar uma macro em que seu conteúdo seja encapsulado por do{…}while(0). Este artigo é um seguimento de uma pergunta deixada em Como Debugar Webassemly?.

Por que Não Usar função em vez de Macro?

Se nós queremos fazer uma macro parecer uma função, por que não usar uma função então? Há vários motivos. A razão particular para se usar macro em vez de função para a implementação de debug é que, por ser uma macro, pode-se pegar informação sobre a localização da macro no código, o que é muito importante enquanto se debuga. A macro é dependente da localização, desta forma podemos logar propriedades de sua localização como, nome do arquivo, função, número de linha, etc…

Imagine que você tenha várias funções que verifica os parâmetros de entrada e loga uma mensagem toda vez que os valores de entrada estao fora do esperado. Achar a localização de uma mensagem logada é muito simples se a mensagem já dê suas coordenadas para você como arquivo, número de linha, etc…

Substituições e a Opção de Compilação -E

Há uma passo antes da compilação em que o compilador executa todas as substituiçoes de macro/defines. Este passo se chama preprocessamento. As substituições podem se tornar complicadas e fica difícil de se entender qual é exatamente o código que será compilado. A opção de compilação -E existe para nos ajudar, ele instrui o compilador a gerar o código fonte depois de todas as substituições. Neste caso o compilador não compila, apenas faz as substituições. Esta opção é extremamente útil quando debugando macros/defines complexas.

Macro de Exemplo

A simplificada macro de debug que segue é usada para nos ajudar a entender porque o while(0) é tão útil. A macro invoca a função Dbg_Prints() se a variável DEBUG_WASM_ENABLED variable não é zero:


#define DBG_LOG(TEXT) \  
do \  
{ \  
  if (DEBUG_WASM_ENABLED) Dbg_Printf(TEXT); \  
} while (0)  

Usando a Macro

O arquivo main.c que segue contém a função Test() que usa a macro DBG_LOG e é base para este artigo.

Um amigo meu uma vez me falou que qualquer coisa com o nome Test traz azar, o risco é seu…


#include <emscripten.h>  
#include "debug-wasm.h"  

void Test(void)  
{  
    DBG_LOG("PASSED HERE!");  
}  

Compilando

Note o uso da opção -E. O arquivo de saída usa a extensão .i, normalmente esta é a extensão usada para este tipo de arquivo.


emcc -E main.c -s WASM=1 -o main.i  

Após o preprocessamento e modificando a indentação para deixar o código mais claro:


void Test(void) 
{ 
 do 
 { 
   if (DEBUG_WASM_ENABLED) Dbg_Printf("PASSED HERE!"); 
 } while (0); 
} 

Como você pode ver, não há nada demais no código gerado, o do{…}while(0) não faz nada especial, ele pode ser apagado sem gerar nenhum efeito colateral. Mas não é sempre assim. Veremos um exemplo um pouco mais interessante.

Ainda Não Convencido, Remova o do{…}while(0) e Me Mostre o Problema

Vamos remover o do{…}while(0) da macro:


#define DBG_LOG(TEXT) if (DEBUG_WASM_ENABLED) Dbg_Printf(TEXT)  

Ainda funciona bem para o exemplo anterior:


void Test(void) 
{ 
 if (DEBUG_WASM_ENABLED) Dbg_Printf("PASSED HERE!"); 
} 

Vamos agora modificar a função Test() para que tenha um if e vamos colocar a nossa macro dentro do if:


void Test(char a)  
{
    if( a!=0 )  
      DBG_LOG("a is NOT zero!");  
    else  
      DBG_LOG("a is zero");  
}

Veja a seguir a função Test() após o preprocessamento. Eu adicionei alguns “{“ para tornar o código mais claro, pois ele está bem confuso!


void Test(char a) 
{  
  if( a!=0 )  
  { 
    if (DEBUG_WASM_ENABLED)  
      Dbg_Printf("a is NOT zero!"); 
    else 
      if (DEBUG_WASM_ENABLED)  
        Dbg_Printf("a is zero");  
  } 
} 

O else esta colocado no lugar errado! O else deveria pertencer ao if( a!=0 ), mas depois das substituições, ele fica pertencendo a if (DEBUG_WASM_ENABLED). Como você pode ver o código gerado não executa o que era esperado dele. Este tipo de problem é muito, muito, difícil de se debugar.

O problema vem do fato de que a macro sem do{…}while(0) tem um if que fica dependurado pronto para se anexar a algum else que se apresente! Ele apresentará um problema toda vez que um eles estiver presente depois da macro. O mesmo problema não é visto com a macro que usa do{…}while(0) porque ele não se anexa a nenhum outro comando.

Porque Não Usar Um Simples Bloco {} ?

Esta foi a pergunta de axilmar no Reddit e respondida por Y_less. {} encontra problemas, pois para se comportar como uma função, a macro tem que aceitar o ponto e vírgula que segue quando se invoca uma função. Vamos ver:

Macro usando {} em vez de do{…}while(0)


#define DBG_LOG(TEXT) \
{ \
    if (DEBUG_WASM_ENABLED) Dbg_Printf(TEXT); \
}

Após preprocessamento


void Test(char a)
{
 if( a!=0 )
   { if ((1)) Dbg_Printf("a is NOT zero!"); };
 else
   { if ((1)) Dbg_Printf("a is zero"); };
}

Erro gerado pelo compilador


main.c:15:2: error: expected expression
        else
        ^
1 error generated.
ERROR:root:compiler frontend failed to generate LLVM bitcode, halting

O ponto e vírgula extra termina com o comando if( a!=0 )! O compilador emite o erro pois o else que se segue não é um comando válido.

Que Outras Macros Estranhas Existe?

Nestes anos já vi muita implementação criativa de macros. Macros são potentes e podem mudar um programa a um ponto de não se saber em que linguagem o programa foi escrito. Por exemplo, veja a macro a seguir que cria uma função loop muito útil:

Em que linguagem o programa foi escrito?


loop(i, 10) 
{ 
   We_Hope_In_Doing_Something_Useful(); 
} 

Here is its C macro definition:


#define loop(x,n) for(int x = 0; x < n; ++x) 

Conclusão

O mundo das macros é potente, difícil e complexo. Implementações não obvias são usadas para se chegar ao resultado querido, mas este uso não ortodoxo da linguagem pode fazer com que o programa fique muito difícel de se compreender. Usando a opção de compilação -E pode ajudar em descobrir qual é o código real que é visto pelo compilador.


Leave a message below. Webassembly is evolving rapidly, please let me know if this post got outdated.

Enjoyed this post?

Don't miss new posts: Share it with your friends:

Você pode gostar...

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *