Las macros son como cualquier otra herramienta: un martillo utilizado en un asesinato no es malo porque es un martillo. Es malvado en la forma en que la persona lo usa de esa manera. Si quieres clavar clavos, un martillo es una herramienta perfecta.
Hay algunos aspectos de las macros que las hacen "malas" (ampliaré cada una de ellas más adelante y sugeriré alternativas):
- No puede depurar macros.
- La expansión macro puede provocar efectos secundarios extraños.
- Las macros no tienen "espacio de nombres", por lo que si tiene una macro que choca con un nombre usado en otro lugar, obtiene reemplazos de macro donde no lo deseaba, y esto generalmente conduce a mensajes de error extraños.
- Las macros pueden afectar cosas de las que no se da cuenta.
Así que ampliemos un poco aquí:
1) Las macros no se pueden depurar.
Cuando tiene una macro que se traduce en un número o una cadena, el código fuente tendrá el nombre de la macro, y muchos depuradores, no puede "ver" a qué se traduce la macro. Así que no sabes realmente qué está pasando.
Reemplazo : use enum
oconst T
Para macros "similares a funciones", debido a que el depurador trabaja en un nivel "por línea de origen donde se encuentre", su macro actuará como una sola declaración, sin importar si es una declaración o cien. Hace que sea difícil averiguar qué está pasando.
Reemplazo : use funciones - en línea si necesita ser "rápido" (pero tenga en cuenta que demasiado en línea no es algo bueno)
2) Las macro expansiones pueden tener efectos secundarios extraños.
El famoso es #define SQUARE(x) ((x) * (x))
y el uso x2 = SQUARE(x++)
. Eso lleva a x2 = (x++) * (x++);
que, incluso si fuera un código válido [1], es casi seguro que no sería lo que el programador quería. Si fuera una función, estaría bien hacer x ++, y x solo se incrementaría una vez.
Otro ejemplo es "si más" en macros, digamos que tenemos esto:
#define safe_divide(res, x, y) if (y != 0) res = x/y;
y entonces
if (something) safe_divide(b, a, x);
else printf("Something is not set...");
De hecho, se convierte en algo completamente incorrecto ...
Reemplazo : funciones reales.
3) Las macros no tienen espacio de nombres
Si tenemos una macro:
#define begin() x = 0
y tenemos un código en 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;
Ahora, ¿qué mensaje de error crees que obtienes y dónde buscas un error [suponiendo que hayas olvidado por completo, o ni siquiera conozcas, la macro de inicio que se encuentra en algún archivo de encabezado que alguien más escribió? [y aún más divertido si incluye esa macro antes de la inclusión - se ahogaría en errores extraños que no tienen absolutamente ningún sentido cuando mira el código en sí.
Reemplazo : Bueno, no hay tanto un reemplazo como una "regla"; solo use nombres en mayúsculas para macros y nunca use nombres en mayúsculas para otras cosas.
4) Las macros tienen efectos que no te das cuenta
Toma esta función:
#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();
}
Ahora, sin mirar la macro, pensaría que begin es una función, que no debería afectar a x.
Este tipo de cosas, y he visto ejemplos mucho más complejos, ¡REALMENTE pueden arruinar tu día!
Reemplazo : no use una macro para establecer x, o pase x como argumento.
Hay ocasiones en las que el uso de macros es definitivamente beneficioso. Un ejemplo es envolver una función con macros para pasar información de archivo / línea:
#define malloc(x) my_debug_malloc(x, __FILE__, __LINE__)
#define free(x) my_debug_free(x, __FILE__, __LINE__)
Ahora podemos usarlo my_debug_malloc
como el malloc regular en el código, pero tiene argumentos adicionales, por lo que cuando llega al final y escaneamos "qué elementos de memoria no se han liberado", podemos imprimir dónde se realizó la asignación para que el El programador puede rastrear la fuga.
[1] Es un comportamiento indefinido actualizar una variable más de una vez "en un punto de secuencia". Un punto de secuencia no es exactamente lo mismo que una declaración, pero para la mayoría de las intenciones y propósitos, eso es lo que deberíamos considerarlo. Por lo tanto, hacerlo x++ * x++
se actualizará x
dos veces, lo que no está definido y probablemente conducirá a valores diferentes en diferentes sistemas y también a valores de resultado diferentes x
.
#pragma
no es macro.