As macros são como qualquer outra ferramenta - um martelo usado em um assassinato não é mau porque é um martelo. É mau da maneira como a pessoa o usa. Se você quer martelar pregos, um martelo é a ferramenta perfeita.
Existem alguns aspectos das macros que as tornam "ruins" (expandirei cada uma delas posteriormente e sugerirei alternativas):
- Você não pode depurar macros.
- A expansão macro pode levar a efeitos colaterais estranhos.
- As macros não têm "espaço de nomes", portanto, se você tiver uma macro que entre em conflito com um nome usado em outro lugar, receberá substituições de macro onde não deseja, e isso geralmente leva a mensagens de erro estranhas.
- As macros podem afetar coisas que você não percebe.
Então, vamos expandir um pouco aqui:
1) As macros não podem ser depuradas.
Quando você tem uma macro que se traduz em um número ou string, o código-fonte terá o nome da macro, e muitos depuradores, você não pode "ver" para o que a macro se traduz. Então você realmente não sabe o que está acontecendo.
Substituição : Use enum
ouconst T
Para macros "semelhantes a funções", porque o depurador trabalha em um nível "por linha de origem onde você estiver", sua macro funcionará como uma única instrução, não importa se é uma instrução ou cem. Torna difícil descobrir o que está acontecendo.
Substituição : use funções - inline se precisar ser "rápido" (mas cuidado, pois muito inline não é uma coisa boa)
2) Expansões macro podem ter efeitos colaterais estranhos.
O famoso é #define SQUARE(x) ((x) * (x))
e o uso x2 = SQUARE(x++)
. Isso leva a x2 = (x++) * (x++);
que, mesmo que fosse um código válido [1], quase certamente não seria o que o programador queria. Se fosse uma função, seria bom fazer x ++ e x só aumentaria uma vez.
Outro exemplo é "if else" em macros, digamos que temos isto:
#define safe_divide(res, x, y) if (y != 0) res = x/y;
e depois
if (something) safe_divide(b, a, x);
else printf("Something is not set...");
Na verdade, torna-se completamente errado ...
Substituição : funções reais.
3) Macros não têm namespace
Se tivermos uma macro:
#define begin() x = 0
e temos algum código em C ++ que usa begin:
std::vector<int> v;
... stuff is loaded into v ...
for (std::vector<int>::iterator it = myvector.begin() ; it != myvector.end(); ++it)
std::cout << ' ' << *it;
Agora, que mensagem de erro você acha que recebeu e onde você procura um erro [presumindo que você tenha esquecido completamente - ou nem mesmo sabido - a macro inicial que reside em algum arquivo de cabeçalho que outra pessoa escreveu? [e ainda mais divertido se você incluir essa macro antes da inclusão - você estará se afogando em erros estranhos que não fazem absolutamente nenhum sentido quando você olha para o próprio código.
Substituição : Bem, não existe tanto uma substituição como uma "regra" - use apenas nomes em maiúsculas para macros e nunca use nomes em maiúsculas para outras coisas.
4) As macros têm efeitos que você não percebe
Faça esta função:
#define begin() x = 0
#define end() x = 17
... a few thousand lines of stuff here ...
void dostuff()
{
int x = 7;
begin();
... more code using x ...
printf("x=%d\n", x);
end();
}
Agora, sem olhar para a macro, você pensaria que begin é uma função, que não deve afetar x.
Esse tipo de coisa, e já vi exemplos muito mais complexos, pode REALMENTE bagunçar o seu dia!
Substituição : não use uma macro para definir x ou passe x como um argumento.
Há momentos em que usar macros é definitivamente benéfico. Um exemplo é envolver uma função com macros para passar informações de arquivo / linha:
#define malloc(x) my_debug_malloc(x, __FILE__, __LINE__)
#define free(x) my_debug_free(x, __FILE__, __LINE__)
Agora podemos usar my_debug_malloc
como o malloc regular no código, mas ele tem argumentos extras, então quando chegar ao final e verificarmos "quais elementos de memória não foram liberados", podemos imprimir onde a alocação foi feita para que o o programador pode rastrear o vazamento.
[1] É um comportamento indefinido atualizar uma variável mais de uma vez "em um ponto de sequência". Um ponto de sequência não é exatamente o mesmo que uma declaração, mas para a maioria das intenções e propósitos, é como devemos considerá-lo. Isso fará com que x++ * x++
seja atualizado x
duas vezes, o que é indefinido e provavelmente levará a valores diferentes em sistemas diferentes e também a valores de resultado diferentes x
.
#pragma
não é macro.