Viva o CPP. Não, não viva o C++... CPP é o C Pre-Processor.
Dentre algumas das muitas coisas que tornam a linguagem C a melhor linguagem que existe, o pré-processador está, com absoluta certeza, entre as primeiras. Dando uma olhada nos fontes do
Linux, você verá algumas s/gambiarras/maravilhas/ do pré-processador. Não entrarei em detalhes sobre todas as funcionalidades do CPP, já que este não é um artigo sobre ele, mas como este é um artigo sobre funções variádicas, achei que incluir uma parte sobre macros variádicas seria interessante.
A definição de uma macro variádica é exatamente igual a declaração das funções variádicas.
#define MACRO(a, b, ...) f(a,b, __VA_ARGS__)
E para "acessar" a lista de parâmetros, utilizamos a "palavra reservada" __VA_ARGS__.
Mas, como já sabemos, o CPP não gera saída em linguagem de máquina. Ele é "simplesmente" um tradutor. Todas as macros são substituídas pelo CPP *antes* de qualquer código chegar ao compilador propriamente dito. Sendo um tradutor, não existe uma pilha para ele tirar os argumentos dela.
Portanto, não há endereços de variáveis como nas funções que vimos, e também não tem jeito de acessar cada argumento separadamente (jeito tem, mas é mais feio que nem o Deadpool - ver referências).
Você pode ver a saída do CPP digitando:
gcc -E arquivo.c
Aviso: os includes normalmente são bem grandes. Pra brincar e ver como o CPP funciona, recomendo não colocar nenhuma include. Apesar de ficarem no início do arquivo, é melhor não encher o terminal. Use apenas defines e veja a saída.
É bem legal. Continuando...
Lembra da nossa função debug()? Podemos reescrevê-la de maneira mais simples se tudo que ela fizer for o que definimos anteriormente (só chamar fprintf() com a lista de argumentos, sem fazer nenhum tipo de verificação de nível de log etc):
#ifndef PRODUCTION
#define debug(fmt, ...) fprintf(stderr, fmt, __VA_ARGS__)
#else
#define debug(fmt, ...)
#endif
Bem melhor não é? Mas temos um problema. Se chamarmos debug() com apenas *um* argumento, o pré-processador gerará código quebrado. Pense:
debug("Variavel big_chunk foi alocada");
Se torna:
fprintf(strerr, "Variavel big_chunk foi alocada", );
Olha aquela virgulinha malvada ali no final... Isso não é bom.
Mas, o CPP é o cara, e nos fornece um meio muito bom de evitar problemas desse tipo, utilizando o operador de concatenação ##. E então nossa macro se torna:
#define debug(fmt, ...) fprintf(stderr, fmt, ## __VA_ARGS__)
Agora ela será traduzida, corretamente, para:
fprintf(strerr, "Variavel big_chunk foi alocada" );
E esse nome grandão, __VA_ARGS__? Podemos trocar também. É só colocar um nome (identificador) para ele, antes das reticências:
#define debug(fmt, args...) fprintf(stderr, fmt, ## args)
Isso é útil se você estiver trabalhando em alguma macro mais complicada, como algumas das belezuras encontradas no Linux.
/* debug_macro.c
* Exemplo de MACRO com argumentos variáveis.
*
* Em desenvolvimento:
* $ gcc -o debug_macro debug_macro.c
*
* Para entregar:
* $ gcc -o debug_macro debug_macro.c -DPRODUCTION
*
* Para ver as expansões:
* $ gcc -E debug_macro.c
*
* Comente o include e retire o ## da macro debug e tente compilar.
* Veja também a saída o preprocessador usando o parâmetro -E e
* procure pela expansão da primeira chamada a debug.
*
* (C) 2016 - Enzo Ferber, <enzoferber@gmail.com>
*/
#include <stdio.h>
#ifndef PRODUCTION
#define debug(fmt, args...) fprintf(stderr, fmt, ## args)
#else
#define debug(fmt, args...)
#endif
int main(void)
{
debug("Teste sem argumentos
");
debug("Verdade inteira: %d
", 42);
debug("A resposta sobre o %s: %.1f
", "O universo e tudo mais", 42.000);
return 0;
}