Bizarro… mas muito útil!
O artigo do Miguel sobre perólas do mundo Java me fez lembrar de um trecho de código com o qual me deparei há um tempo atrás que me parecia também muito bizarro… até eu sacar um pouco depois a imensa utilidade dele!
O código, em linguagem C, era algo como o seguinte:
do {
__alguma_coisa;
} while (false);
Em princípio fazer uma construção de laço para executar algo apenas uma vez parece algo bem bizarro, não?! Diria que é quase um primo-irmão do if (liberado==true). Foi exatamente o que eu achei. Mas colocando esta construção dentro do contexto em que eu a achei eu consegui ver (depois de alguma pesquisa, é claro) que ela é extremamente útil em alguns casos.
A primeira vez que eu me deparei com esta construção eu estava analisando o código-fonte do GNUChess, um jogo de xadrez de código livre. Como eu sei que o pessoal que escreve código livre algumas vezes utiliza umas construções bem diferentes do que normalmente estamos acostumados, resolvi investigar o porquê deles estarem utilizando esta construção. Afinal de contas, se este código é livre e várias pessoas já devem tê-lo revisado, não havia motivo para um laço aparentemente tão inútil estar lá sem alguma utilidade verdadeira, não é mesmo?!
E foi só pesquisar um pouco sobre o assunto para enteder melhor o que estava acontecendo. As construções como a acima só apareciam em definições de macros. Nunca em código “normal”. Para quem talvez não tenha muita familiaridade com macros em C, elas são basicamente códigos que você define e que serão literalmente expandidos pelo compilador onde seu identificador aparecer no código. Ou seja, um código assim:
#define macro_exemplo printf("Macro de exemplo!\n");
int main(void) {
macro_exemplo
return 0;
}
seria entendido pelo compilador, após o processamento das macros, da seguinte forma:
int main(void) {
printf("Macro de exemplo!\n");
return 0;
}
Ou seja, o preprocessador de código do compilador substitui a referência à macro_exemplo pela sua definição literal. No caso do nosso código bizarro, teríamos que definir a macro da seguinte forma:
#define macro_exemplo2 do { __alguma_coisa; } while (false);
ou ainda:
#define macro_exemplo3 do { \
__alguma_coisa; \
} while (false);
Note que uma macro deve ser definida sem quebras de linhas ou com quebras de linhas sinalizadas pelo caracter de escape barra invertida (’\').
Mas, ainda assim, não haveria motivo para o laço na minha macro, certo? Bom, se você pensar na macro como um código por si só, talvez isto esteja certo, mas não podemos esquecer que este código será copiado literalmente para algum outro lugar sempre que o identificador da macro for referenciado pelo programa. E muitas vezes não queremos fazer macros simples como as que eu mostrei no meu exemplo. Por exemplo, podemos querer fazer algo mais complexo como o seguinte:
#define IMPRIME_ESPACOS(n) for (int j = 0; j < n; j++) printf(" ");
#define ROTULO(rot) printf("L%d:", rot); do { \
int n = numeroDigitos(rot); \
IMPRIME_ESPACOS(6 - (n + 2))\
} while (false);
A macro IMPRIME_ESPACOS(n) imprime uma quantidade de espaços em branco representadas pelo “argumento” n. Estou usando argumento entre aspas porque o argumento de uma macro em C se comporta de maneira diferente de um argumento de função, já que não há checagem de tipo e sim uma simples substituição do valor passado como argumento da macro nas suas referências dentro da própria macro. Já a macro ROTULO(rot) recebe como “argumento” um rótulo e imprime Lrot seguido de um numero de espaços de tal forma que todas as linhas que chamarem a macro ROTULO(rot) estarão identadas na mesma posição (desde que o rótulo tenha menos de 6 caracteres, que era o limite utilizado no programa a partir do qual tirei o código de exemplo).
Alguém certamente já bateu o olho no código acima e deve ter dito: poxa, eu poderia chamar a função
numeroDigitos(rot) dentro de IMPRIME_ESPACOS na definição da macro ROTULO. Não podemos nos esquecer no entanto que macros não são chamadas de função e que onde se lê IMPRIME_ESPACOS em ROTULO dever-se-ia ler na verdade a definição de IMPRIME_ESPACOS. Neste caso, se passássemos uma chamada de numeroDigitos como “argumento” de IMPRIME_ESPACOS, a função numeroDigitos seria chamada dentro do loop definido em IMPRIME_ESPACOS sempre que a condição de parada do loop fosse ser calculada. Ou seja, teríamos um número excessivo e desnecessário de chamadas a numeroDigitos. Para solucionar este eventual problema, decidi criar uma variavel para armazenar o valor da chamada de numeroDigitos e passar esta variavel como argumento para IMPRIME_ESPACOS. O problema é que dependendo de onde este código for ser substituído pode não ser permitido ter declarações de variáveis.
Neste último caso (e em muitos outros), o código mostrado no início deste post, por mais bizarro que possa parecer a primeira vista, pode ser a solução, pois o uso do do { ... } while (false); torna possível a criação de um contexto local dentro das chaves do falso loop na macro. Dentro deste contexto é possível criar-se quantas variáveis se queira ou, por exemplo, encadear comandos dentro de uma macro que não ficarão perdidos se esta macro for utilizada dentro de um if-else.
Ah, e se alguém ficou curioso, procure na Internet: vocês verão muitos outros usos do do-while (false). Sinceramente, muitos deles, na minha opinião são dispensáveis e só servem para complicar código que poderia ser escrito de outra forma. E, pra falar a verdade, a única construção que eu já achei em códigos livres foi esta que eu cito aqui, dentro de macros. É preciso ter dicernimento na hora de usar este tipo de construção. Aliás, para falar a verdade, mesmo este caso que eu descrevi aqui, que eu acho interessante e útil eu só usária em últimos casos em códigos profissionais.

Olá
Essa construção é usada somente para pode criar variáveis no contexto local?
Fazendo a sua macro, somente com as chaves {} seria possível criar um contexto local, certo?
Algo como:
#define ROTULO_SEM_WHILE(rot) printf(”L%d:”, rot); { \
int n = numeroDigitos(rot); \
IMPRIME_ESPACOS(6 - (n 2))\
}
Não teria o mesmo efeito?
Fiquei curioso hehe, mas se for somente para a criação de um escopo local, o do-while é desnecessário
Eu estava pensando a mesma coisa. Só não sei de cabeça como é que o ANSI C lida com a parte de escopo de variáveis dentro de loops, ou se é possível fazer um bloco de código.
De qualquer jeito, não vejo diferença entre fazer uma macro que usa um do{}while(false) e uma macro que faça if(1){}.
Também não sei de cabeça como é isso no Ansi C, mas acho que no início de blocos pode criar variáveis na boa.
No C funciona, eu já testei.
Insane e Raphael,
Confesso que eu não pensei muito sobre as coisas que vocês estão comentando. Eu simplesmente vi o código uma vez, fiquei curioso, fui atrás, achei útil e comecei a usar em alguns projetos pessoais.
Mas a indagação de vocês faz todo o sentido. Aí fui lá eu testar. Fiz um pequeno programa de teste e compilei com “gcc -std=c99 -pedantic -Wall -o test test.c”. Tive que alterar um pouco as macros porque elas tinham construções não totalmente compatíveis com C99 (como, por exemplo, declaração de variável dentro dos argumentos do loop for). O resultado é que usando apenas chaves para delimitar o bloco funciona também. Ou seja, o do-while(false) realmente parece ser desnecessário.
Eu li em um fórum que este tipo de construção é útil quando se quer especificar statements em C/C dentro de macros. No entanto, pelo teste que eu fiz usar apenas chaves já seria suficiente para isso. Pelo menos no caso que eu testei (não sei se eu consigo testar casos genéricos de todas as situações em que é possível se colocar statements em C/C ).
Como o do-while(false) certamente é eliminado pelo compilador durante a compilação, imagino que o fato de este tipo de construção ainda aparecer deve ser por estilo de programação ou alguma coisa assim. Mas, tanto faria usar apenas as chaves ou até mesmo um if (1) imagino eu.
Concluíndo, se tem alguma coisa de realmente especial em se usar do-while (false) ao invés de alguma outra construção como as que vocês sugeriram, eu não sei dizer. Alguém especialista em C por aí que saiba com segurança a resposta para esta pergunta?
Bom dia pessoas,
Pesquisei um pouco, e essa trick é válida sim.
Visitem: http://www.noveltheory.com/TechPapers/while.htm