Como escrever macros em C
A linguagem C em muitos casos nos exige escrever muitas linhas de código para fazer algo relativamente simples, e não são raros os casos em que precisamos repetir estas rotinas de novo e de novo. Bibliotecas como a libssh
e a libusb
nos dão a responsabilidade de inicializar subsistemas, realizar verificações de rotina após cada operação… e tudo isso acaba engordando o nosso código com pouco significado semântico.
Neste tutorial eu vou te mostrar como simplificar estas rotinas repetitivas com macros em C, e como utilizar sua estrutura super poderosa: as macros de argumentos variáveis.
Caso você prefira um apoio em vídeo, tá aqui explicadinho o artigo todo e você consegue acompanhar com código conforme vou escrevendo os exemplos:
O que são macros na linguagem C?
Macros na linguagem C são pedaços de código que você pode “inserir” em qualquer lugar. Você pode reconhecer uma macro através da palavra-chave #define
: se você vir esta palavra-chave antes de algum texto, este texto é uma macro.
Ao referenciar uma macro em seu código, o conteúdo desta macro é copiado antes de compilar seu código.
Abaixo um exemplo de programa em C que define uma macro CODAMOS
, atribui um valor "codamos.com.br"
e a utiliza no contexto da função printf().
#include <stdio.h>
#define CODAMOS "codamos.com.br"
int main(void)
{
printf("Ola, %s\n", CODAMOS);
}
Eu comentei que as macros são copiadas quando referenciadas. Acontece que antes de compilar seu código, ele vai se transformar no seguinte:
#include <stdio.h>
int main(void)
{
printf("Ola, %s\n", "codamos.com.br");
}
Repare que ao compilar o código, a constante CODAMOS
foi substituída por seu valor definido: "codamos.com.br"
.
Macros como funções em C
Como vimos acima macros podem ser utilizadas para substituir um símbolo criado pela palavra-chave #define
por algum outro texto ou código. É possível fazer com que macros invoquem funções, como no exemplo abaixo:
#include <stdio.h>
#define ECHO(msg) printf(msg);
int main(void)
{
ECHO("Ola codamos!\n");
}
Podemos inclusive fazer com que nossas macros automatizem certas rotinas, como por exemplo indicar o nível de log na mensagem:
#include <stdio.h>
#define LOG_ERR(msg) fprintf(stderr, msg)
#define LOG_INFO(msg) fprintf(stdout, msg)
int main(void)
{
LOG_INFO("Inicializando o programa...\n");
LOG_ERR("Falha ao inicializar o programa\n");
return 1;
}
Repare que LOG_INFO()
e LOG_ERR()
não são funções. São apenas macros que se comportam como função. E por baixo dos panos tomam algumas decisões fundamentais e repetitivas como qual stream
utilizar para a saída da mensagem: stdout
ou stderr
.
Com macros podemos inclusive tornar este mecanismo ainda mais poderoso para indicar, por exemplo, qual a linha e arquivo onde um erro aconteceu. Veja:
#include <stdio.h>
#define eprintf(msg) fprintf(stderr, "[%s:%d] ", __FILE__, __LINE__); fprintf(stderr, msg);
int main(void)
{
eprintf("Falha ao inicializar o programa\n");
}
Ao executar o código acima teremos uma saída mais ou menos assim:
[main.c:7] Falha ao inicializar o programa
E se quisermos utilizar um printf()
com vários argumentos para interpolar strings? Neste caso precisamos de macros com argumentos variáveis.
Macros com argumentos variáveis
A função printf()
(e outras parecidas como o fprintf()
) podem receber N argumentos de acordo com o argumento “formato” que é passado. O exemplo abaixo recebe uma string “codamos.com.br” e um inteiro com valor 10
:
printf("Site: %s, Nota: %d", "codamos.com.br", 10);
Como podemos transferir estes valores para a nossa macro?
Supondo que a nossa macro se chame LOG_INFO
e precise indicar o arquivo, linha e texto onde a mensagem foi enviada, podemos escrever algo assim:
#include <stdio.h>
#define LOG_INFO(...) fprintf(stderr, "[INFO] %s:%d: ", __FILE__, __LINE__); fprintf(stderr, __VA_ARGS__);
int main(void)
{
LOG_INFO("Inicializando o programa '%s'.\n", "codamos.com.br");
}
A macro acima utiliza a palavra-chave __VA_ARGS__
que justamente expande para todas as variáveis recebidas como parâmetro, representadas por ...
, quando compilamos o programa. O termo para esta tecnica é Variadic Macros.
A saída do comando acima deverá ser algo como:
[INFO] main.c:7: Inicializando o programa ‘codamos.com.br’
Isto porque os dois parâmetros da macro LOG_INFO
foram a string "Inicializando o programa '%s'.\n'"
e a string "codamos.com.br"
. Estes dois parâmetros foram armazenados no símbolo __VA_ARGS__
e repassados para a função fprintf()
no contexto daquela macro.
Macros com multiplas linhas de código
Como vimos nos exemplos anteriores, macros ficam bagunçadas rapidinho. Porque quanto mais funcionalidade a gente adicionar num único #define
, maior a linha daquela macro.
Mas é possível quebrar as linhas de uma macro para que fique mais fácil de entender. Para isto é necessário adicionar uma barra invertida ao final de cada linha, veja como:
#include <stdio.h>
#define LOG_INFO(__VA_ARGS__) \
fprintf(stderr, "[INFO] %s:%d: ", __FILE__, __LINE__); \
fprintf(stderr, __VA_ARGS__);
int main(void)
{
LOG_INFO("Inicializando o programa '%s'.\n", "codamos.com.br");
}
A mensagem agora deverá ser quase a mesma de antes, o que muda é apenas a linha onde foi invocada a mensagem porque agora a macro possui múltiplas linhas:
[INFO] main.c:9: Inicializando o programa ‘codamos.com.br’
Comentários