Siempre vi ejemplos y casos en los que usar una macro es mejor que usar una función.
¿Alguien podría explicarme con un ejemplo la desventaja de una macro en comparación con una función?
Siempre vi ejemplos y casos en los que usar una macro es mejor que usar una función.
¿Alguien podría explicarme con un ejemplo la desventaja de una macro en comparación con una función?
Respuestas:
Las macros son propensas a errores porque se basan en la sustitución textual y no realizan verificación de tipos. Por ejemplo, esta macro:
#define square(a) a * a
funciona bien cuando se usa con un número entero:
square(5) --> 5 * 5 --> 25
pero hace cosas muy extrañas cuando se usa con expresiones:
square(1 + 2) --> 1 + 2 * 1 + 2 --> 1 + 2 + 2 --> 5
square(x++) --> x++ * x++ --> increments x twice
Poner paréntesis alrededor de los argumentos ayuda, pero no elimina por completo estos problemas.
Cuando las macros contienen varias declaraciones, puede tener problemas con las construcciones de flujo de control:
#define swap(x, y) t = x; x = y; y = t;
if (x < y) swap(x, y); -->
if (x < y) t = x; x = y; y = t; --> if (x < y) { t = x; } x = y; y = t;
La estrategia habitual para solucionar este problema es colocar las declaraciones dentro de un bucle "do {...} while (0)".
Si tiene dos estructuras que contienen un campo con el mismo nombre pero semántica diferente, la misma macro podría funcionar en ambas, con resultados extraños:
struct shirt
{
int numButtons;
};
struct webpage
{
int numButtons;
};
#define num_button_holes(shirt) ((shirt).numButtons * 4)
struct webpage page;
page.numButtons = 2;
num_button_holes(page) -> 8
Finalmente, las macros pueden ser difíciles de depurar, produciendo extraños errores de sintaxis o errores de tiempo de ejecución que tienes que expandir para entenderlos (por ejemplo, con gcc -E), porque los depuradores no pueden recorrer las macros, como en este ejemplo:
#define print(x, y) printf(x y) /* accidentally forgot comma */
print("foo %s", "bar") /* prints "foo %sbar" */
Las funciones y constantes en línea ayudan a evitar muchos de estos problemas con las macros, pero no siempre son aplicables. Cuando las macros se utilizan deliberadamente para especificar el comportamiento polimórfico, el polimorfismo involuntario puede ser difícil de evitar. C ++ tiene una serie de características, como plantillas, para ayudar a crear construcciones polimórficas complejas de forma segura sin el uso de macros; consulte El lenguaje de programación C ++ de Stroustrup para obtener más detalles.
x++*x++
no se puede decir que la expresión se incremente x
dos veces; de hecho, invoca un comportamiento indefinido , lo que significa que el compilador es libre de hacer lo que quiera: podría incrementar x
dos veces, una vez, o nada en absoluto; podría abortar con un error o incluso hacer que los demonios salgan volando de tu nariz .
Funciones macro :
Características de la función :
Los efectos secundarios son importantes. Este es un caso típico:
#define min(a, b) (a < b ? a : b)
min(x++, y)
se expande a:
(x++ < y ? x++ : y)
x
se incrementa dos veces en la misma declaración. (y comportamiento indefinido)
Escribir macros de varias líneas también es un problema:
#define foo(a,b,c) \
a += 10; \
b += 10; \
c += 10;
Requieren un \
al final de cada línea.
Las macros no pueden "devolver" nada a menos que lo convierta en una sola expresión:
int foo(int *a, int *b){
side_effect0();
side_effect1();
return a[0] + b[0];
}
No se puede hacer eso en una macro a menos que use la declaración de expresión de GCC. (EDITAR: aunque puede usar un operador de coma ... lo pasó por alto ... pero aún podría ser menos legible).
Orden de operaciones: (cortesía de @ouah)
#define min(a,b) (a < b ? a : b)
min(x & 0xFF, 42)
se expande a:
(x & 0xFF < 42 ? x & 0xFF : 42)
Pero &
tiene menor precedencia que <
. Entonces 0xFF < 42
se evalúa primero.
min(a & 0xFF, 42)
#define SQUARE(x) ((x)*(x))
int main() {
int x = 2;
int y = SQUARE(x++); // Undefined behavior even though it doesn't look
// like it here
return 0;
}
mientras:
int square(int x) {
return x * x;
}
int main() {
int x = 2;
int y = square(x++); // fine
return 0;
}
struct foo {
int bar;
};
#define GET_BAR(f) ((f)->bar)
int main() {
struct foo f;
int a = GET_BAR(&f); // fine
int b = GET_BAR(&a); // error, but the message won't make much sense unless you
// know what the macro does
return 0;
}
Comparado con:
struct foo {
int bar;
};
int get_bar(struct foo *f) {
return f->bar;
}
int main() {
struct foo f;
int a = get_bar(&f); // fine
int b = get_bar(&a); // error, but compiler complains about passing int* where
// struct foo* should be given
return 0;
}
En caso de duda, utilice funciones (o funciones en línea).
Sin embargo, las respuestas aquí explican principalmente los problemas con las macros, en lugar de tener una visión simple de que las macros son malas porque los accidentes tontos son posibles.
Puede ser consciente de los peligros y aprender a evitarlos. Luego, use macros solo cuando haya una buena razón para hacerlo.
Hay ciertos casos excepcionales en los que el uso de macros tiene ventajas, entre los que se incluyen:
va_args
. __FILE__
, __LINE__
, __func__
). verifique las condiciones previas / posteriores, assert
en caso de falla o incluso afirmaciones estáticas para que el código no se compile en caso de uso incorrecto (principalmente útil para compilaciones de depuración).struct
miembros estén presentes antes de lanzar func(FOO, "FOO");
, podría definir una macro que expanda la cadena por ustedfunc_wrapper(FOO);
inline
funciones pueden ser una opción) .Es cierto que algunos de estos se basan en extensiones del compilador que no son estándar C. Lo que significa que puede terminar con un código menos portátil, o tener que hacerlo ifdef
, por lo que solo se aprovechan cuando el compilador lo admite.
Teniendo en cuenta esto, ya que es una de las causas más comunes de errores en las macros (pasando, x++
por ejemplo, cuando una macro puede incrementarse varias veces) .
Es posible escribir macros que eviten efectos secundarios con múltiples instancias de argumentos.
Si desea tener una square
macro que funcione con varios tipos y tenga soporte C11, puede hacer esto ...
inline float _square_fl(float a) { return a * a; }
inline double _square_dbl(float a) { return a * a; }
inline int _square_i(int a) { return a * a; }
inline unsigned int _square_ui(unsigned int a) { return a * a; }
inline short _square_s(short a) { return a * a; }
inline unsigned short _square_us(unsigned short a) { return a * a; }
/* ... long, char ... etc */
#define square(a) \
_Generic((a), \
float: _square_fl(a), \
double: _square_dbl(a), \
int: _square_i(a), \
unsigned int: _square_ui(a), \
short: _square_s(a), \
unsigned short: _square_us(a))
Esta es una extensión del compilador compatible con GCC, Clang, EKOPath e Intel C ++ (pero no MSVC) ;
#define square(a_) __extension__ ({ \
typeof(a_) a = (a_); \
(a * a); })
Entonces, la desventaja de las macros es que necesita saber cómo usarlas para empezar, y que no son tan compatibles.
Un beneficio es que, en este caso, puede usar la misma square
función para muchos tipos diferentes.
No se repite la verificación de tipo de los parámetros y el código, lo que puede provocar que el código se sobrecargue. La sintaxis de macros también puede llevar a cualquier número de casos extremos extraños en los que el punto y coma o el orden de precedencia pueden interferir. Aquí hay un enlace que demuestra algo de maldad macro.
Un inconveniente de las macros es que los depuradores leen el código fuente, que no tiene macros expandidas, por lo que ejecutar un depurador en una macro no es necesariamente útil. No hace falta decir que no puede establecer un punto de interrupción dentro de una macro como puede hacerlo con las funciones.
Las funciones comprueban el tipo. Esto le brinda una capa adicional de seguridad.
Añadiendo a esta respuesta ...
Las macros son sustituidas directamente en el programa por el preprocesador (ya que básicamente son directivas del preprocesador). Entonces, inevitablemente, usan más espacio de memoria que una función respectiva. Por otro lado, una función requiere más tiempo para ser llamada y devolver resultados, y esta sobrecarga se puede evitar usando macros.
Además, las macros tienen algunas herramientas especiales que pueden ayudar con la portabilidad del programa en diferentes plataformas.
No es necesario asignar a las macros un tipo de datos para sus argumentos en contraste con las funciones.
En general, son una herramienta útil en programación. Y tanto las macroinstrucciones como las funciones se pueden utilizar según las circunstancias.
No noté, en las respuestas anteriores, una ventaja de las funciones sobre las macros que creo que es muy importante:
Las funciones se pueden pasar como argumentos, las macros no.
Ejemplo concreto: desea escribir una versión alternativa de la función estándar 'strpbrk' que aceptará, en lugar de una lista explícita de caracteres para buscar dentro de otra cadena, una función (puntero a a) que devolverá 0 hasta que un carácter sea encontró que pasa alguna prueba (definida por el usuario). Una razón por la que podría querer hacer esto es para poder explotar otras funciones de biblioteca estándar: en lugar de proporcionar una cadena explícita llena de puntuación, podría pasar 'ispunct' de ctype.h, etc. Si 'ispunct' se implementó solo como una macro, esto no funcionaría.
Hay muchos otros ejemplos. Por ejemplo, si su comparación se realiza mediante una macro en lugar de una función, no puede pasarla a 'qsort' de stdlib.h.
Una situación análoga en Python es 'imprimir' en la versión 2 frente a la versión 3 (declaración no aceptable frente a función aceptable).
Si pasa la función como un argumento a la macro, se evaluará cada vez. Por ejemplo, si llama a una de las macros más populares:
#define MIN(a,b) ((a)<(b) ? (a) : (b))
como eso
int min = MIN(functionThatTakeLongTime(1),functionThatTakeLongTime(2));
functionThatTakeLongTime se evaluará 5 veces, lo que puede reducir significativamente el rendimiento