Macros
Macros são diretivas de preprocessamento que realizam uma substituição textual.
Macros não tem escopo, portanto mesmo se ela for definida dentro de uma função, poderá ser utilizada fora dela.
A sintaxe para definirmos novas macros é :
//Macro sem conteúdo
#define BUILD_DEBUG
//Macro sem parâmetros
#define TEXTO_ANTIGO "Texto novo"
//Macro com parâmetros
#define MAXIMO(X,Y) (((X) > (Y)) ? (X) : (Y))
Dessa forma quando escrevermos :
BUILD_DEBUG; //Será substituido por nada
TEXTO_ANTIGO; //Será substituido por "Texto novo"
//Resultará em 5
//expressão ((5) > (3) ? (5) : (3))
int test = MAXIMO(5,3);
Lembrando que no C23
, podemos substituir o uso de macros para definições de constantes utilizando variáveis como constexpr
ou até enum
, o problema de constexpr
é que ele é bem recente, acaba sendo problemático quando desejamos fazer bibliotecas que funcionem com versões anteriores do C, no C++ essa noção já é mais comum pois constexpr
foi adicionado desde o C++11
.
Funções-Macros
Devido a falta de algumas funcionalidades alto nível na linguagem, é normal que programadores utilizem macros com parâmetros para implementar algumas funcionalidades utilizando macros.
A vantagem de utilizar macros é que podemos fazer coisas que normalmente não seriam possíveis utilizando apenas funções, a desvantagem, é que abrimos portas para que o programador possa implementar coisas realmente imprevisíveis, difíceis de testar, etc.
É preferível utilizar funções normais quando elas são suficientes, mas as funções macros tem seu espaço, o importante é saber utiliza-las de forma consciente, algo que programadores adquirem com experiência.
Para macros de múltiplas linhas é comum o uso de do while(0)
, que pode inicialmente parecer estranho por ser um "loop que não se repete" mas é a única forma de garantir que a macro funcione em um if
de uma linha :
#include <stdio.h>
#include <stdbool.h>
#define print_forced1(X) {printf(X); fflush(stdout);}
#define print_forced2(X) \
do { \
printf(X); fflush(stdout); \
} while(0)
//Neste exemplo, ocorre um "erro" de compilação
//Pois "print_forced1" expande para uma chave com
//ponto e virgula no final, causando erros
bool forcePrint = true;
if(forcePrint)
print_forced1("Teste");
else
printf("Teste");
//Enquanto isso, utilizar "print_forced2" funciona
//Pois o loop "do while(0)" mantem as chaves dentro dele
// se tornando um único statement
if(forcePrint)
print_forced2("Teste");
else
printf("Teste");
Existem também dois erros que são muito "comuns" quando utilizamos macros:
- A falta de parenteses, que pode causar uma grande dor de cabeça devido as diferentes ordens de precedências dos operadores
- A possibilidade de repetirmos parâmetros quando há uma chamada de função.
Podemos exemplificar os dois casos ao implementar uma função-macro para obter o valor máximo :
#include <math.h>
#define MATH_MAX_1(X,Y) (X > Y ? X : Y)
#define MATH_MAX_2(X,Y) ((X) : (Y) ? (X) : (Y))
//Resulta em 3, pois teremos
//1 ^ (10 > 3) -> 0, pois XOR inverte o bit
// 0 ? 11 : 3 -> 3, pois temos 0 no ternário
const int test = MATH_MAX_1(1 ^ 10, 3);
//Resulta em 11, pois teremos
//1^10 = 11
// (11) > (3) ? (11) : (3)
const int test2 = MATH_MAX_2(1 ^ 10, 3);
//Neste caso, teremos TRÊS chamadas
//de função, pois expandirá para:
// sqrtf(2) > pow(1.19,2) ? sqrtf(2) : pow(1.19,2)
float test3 = MATH_MAX_2(sqrtf(2), pow(1.19,2));
Fica a dica :
- Sempre certifique-se de botar parenteses pois isso garante um funcionamento adequado das macros para qualquer entrada
- Evite usar funções como parâmetros em macros, ou certifique-se de documentar quando o usuário pode utilizá-las ou deve evitá-las com a sua macro
- Se for fazer uma função macro, certifique-se que ela se comporta como uma chamada de função, utilizando
do while(0)
caso necessário (evitando imprevisibilidades) - Sempre que pensar em fazer uma função-macro, pense bem se essa solução é viável e se não existe outra forma melhor
Macros com argumentos variádicos
Desde o C99
podemos utilizar argumentos variádicos em macros, de forma similar aos argumentos variádicos em funções.
Ao utilizar ...
como o último parâmetro de uma macro, podemos colocar quantos argumentos quisermos naquele ponto, os argumentos podem ser acessados na macro ao digitar __VA_ARGS__
.
Exemplo :
#include <stdio.h>
#define PRINT_ERRO(FORMAT, ...) fprintf(stderr, FORMAT, __VA_ARGS__)
//Esta macro
PRINT_ERRO("Erro %d no arquivo %s\n", 30, "main.c");
//Será substituida por
fprintf(stderr, "Erro %d no arquivo %s\n", 30, "main.c");
Observamos também que no exemplo acima, como foi colocada uma virgula na macro, se ...
for vazio, teremos um erro de compilação :
//Erro de compilação!
PRINT_ERRO("Erro!\n");
//Pois isso expande para
fprintf(stderr, "Erro!\n",);
Para isso, desde o C23
, podemos utilizar __VA_OPT__
para indicar algo que só será adicionado caso __VA_ARGS__
não seja vazio :
#include <stdio.h>
#define PRINT_ERRO(FORMAT, ...) fprintf(stderr, FORMAT __VA_OPT__(,) __VA_ARGS__)
PRINT_ERRO("Erro!\n");
Manipulação de argumentos de macro
Existem duas operações que podem ser realizadas utilizando argumentos de macros :
- Concatenação
- Conversão para string
Para concatenar um argumento de macro, utilizamos ##
dentro da macro :
#define JUNTA_NOME(X,Y) X##Y
//Neste caso geramos um nome com "Vector_Grow"
void JUNTA_NOME(Vector, _Grow)()
{
//Código da função
}
As coisas se tornam um pouco mais complicadas caso quisermos concatenar o conteúdo de macros e não seus nomes, para isso é necessário usar uma macro para forçar a expansão das macros antes de realizar a operação:
#define JUNTA_NOMES_INTERNO1(X,Y) X##Y
#define JUNTA_NOMES(X,Y) JUNTA_NOMES_INTERNO1(X,Y)
int main()
{
//Causa um erro, pois "JUNTA_NOMES_INTERNO1"
//juntará ambos, gerando "__DATE____FILE__"
JUNTA_NOMES_INTERNO1(__DATE__,__FILE__);
//Neste caso, as macros são repassadas a outra macro
//De forma que "__DATE__" e "__FILE__" sejam expandidas
//E se tornem realmente strings com a data e nome do arquivo
JUNTA_NOMES(__DATE__,__FILE__);
}
Para converter simbolos para string, utilizamos #
:
#define NOME_VARIAVEL(X) #X
int main()
{
int test = 5;
//Esta expressão se tornará
const char *nome = NOME_VARIAVEL(test);
//Isso :
const char *nome = "test";
}
Remover definições de macros
Podemos utilizar a diretiva #undef
para remover uma macro já criada.
O uso é muito simples, basta utilizar #undef NOME_MACRO
e ela será removida.
Isso é útil por exemplo quando queremos limitar uma macro a uma única função, pois macros não tem escopo.
Macros pre-definidas
Algumas macros são predefinidas pelo padrão do C e estão sempre presentes :
__STDC__
: Tem o valor1
caso a implementação respeite o padrão do C.__STDC_VERSION__
: Introduzido noC95
, indica a versão do C utilizada, onde :199409L
=C95
199901L
=C99
201112L
=C11
201710L
=C17
202311L
=C23
__STDC_HOSTED__
: Introduzido noC99
, indica1
se a versão roda num sistema operacional e0
caso não haja sistema operacional (chamado freestanding).__FILE__
: Se torna uma string literal com o nome do arquivo atual.__LINE__
: Se torna o número da linha atual do código fonte.__DATE__
: Se torna a data que o programa foi gerado, no formatoMmmm dd yyyy
, o nome do mês se comporta como se gerado porasctime
.__TIME__
: Se torna o horário em que o programa foi gerado, no formatohh:mm:ss
, como se gerado porasctime
.__STDC_UTF_16__
: Obrigatório desdeC23
, indica1
sechar16_t
utilizar o encodingUTF-16
.__STDC_UTF_32__
: Obrigatório desdeC23
, indica1
sechar32_t
utilizar o encodingUTF-32
.
Outras macros, podem opcionalmente serem predefinidas pela implementação:
__STDC_ISO_10646__
: Se torna um inteiro no formatoyyyymmL
sewchar_t
usar Unicode, a data indica a última revisão do Unicode suportada.__STDC_IEC_559__
: Introduzido noC99
, se torna1
seIEC 60559
for suportado (depreciado noC23
)__STDC_IEC_559_COMPLEX__
: Introduzido noC99
, se torna1
se números complexos forem suportados.__STDC_UTF_16___
: Introduzido noC11
, indica1
sechar16_t
utilizar o encodingUTF-16
.__STDC_UTF_32___
: Introduzido noC11
, indica1
sechar32_t
utilizar o encodingUTF-32
.__STDC_MB_MIGHT_NEQ_WC__
: Introduzido noC99
, indica1
se comparações de caractere como'x' == L'x'
podem resultar em falso.__STDC_ANALYZABLE__
:Introduzido noC11
, indica1
quando o compilador é limitado a não modificar o comportamento do código em certos casos de comportamento indefinido.__STDC_LIB_EXT1__
: Introduzido noC11
, se torna201112L
se as funções "seguras" do Annex K estão disponíveis.__STDC_NO_ATOMICS__
: Introduzido noC11
, se torna1
caso tipos atômicos não sejam suportados.__STDC_NO_COMPLEX__
: Introduzido noC11
, se torna1
caso números complexos não sejam suportados.__STDC_NO_THREADS__
: Introduzido noC11
, se torna1
se as funções padrões de threads não seja suportadas.__STDC_NO_VLA__
: Introduzido noC11
, se torna1
se arrays de tamanho variável não forem suportados.__STDC_IEC_60559_BFP__
: Introduzido noC23
, se torna202311L
se tipos adicionais de ponto flutuante forem suportados (_FloatN
,_FloatN_t
).__STDC_IEC_60559_DFP__
: Introduzido noC23
, se torna202311L
se pontos flutuantes decimais forem suportados (_Decimal32
,_Decimal64
,_Decimal128
).__STDC_IEC_60559_COMPLEX__
: Introduzido noC23
, se torna202311L
se números complexos forem suportados (_Complex
e_Imaginary
).__STDC_IEC_60559_TYPES__
: Introduzido noC23
, se torna202311L
se a implementação implementa qualquer um dos tipos doIEC_60559
.
Diretiva #line
A diretiva #line
pode ser utilizada para modificar os valores de __FILE__
e __LINE__
, a sintaxe é :
#line num_linha
: Modifica o número da linha utilizado em__LINE__
.#line num_linha "nome arquivo"
: Modifica o nome do arquivo utilizado em__FILE__
e o número da linha em__LINE__
.