Última atualização:
Macros em C
Macros em C

Como escrever macros em C

Nawarian
Nawarian 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