Macro vs Función en C

Siempre vi ejemplos y casos en los que usar una macro es mejor que usar la función.

¿Podría alguien explicarme con un ejemplo la desventaja de una macro en comparación con una función?

Las macros son propensas a errores porque se basan en la sustitución textual y no realizan la verificación de tipos. Por ejemplo, esta macro:

#define square(a) a*a 

funciona bien cuando se utiliza 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 sentencias múltiples, puede tener problemas con las construcciones de flujo de control:

 #define swap(x,y) t=x; x=y; y=t; if(x if(x if(x 

La estrategia habitual para solucionar esto es colocar las instrucciones dentro de un ciclo "do {...} while while (0)".

Si tiene dos estructuras que contienen un campo con el mismo nombre pero diferente semántica, 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 errores de syntax extraños o errores de tiempo de ejecución que debe expandir para comprender (por ejemplo, con gcc -E), ya que los depuradores no pueden pasar por las macros, como en este ejemplo:

 #define print(x, y) printf(xy) /* 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. Donde las macros se usan deliberadamente para especificar el comportamiento polimórfico, el polymorphism involuntario puede ser difícil de evitar. C ++ tiene una serie de funciones, como plantillas, para ayudar a crear construcciones polimórficas complejas de una manera segura sin el uso de macros; ver el lenguaje de progtwigción C ++ de Stroustrup para más detalles.

Los efectos secundarios son grandes. Aquí hay 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 statement. (y comportamiento indefinido)


Escribir macros de varias líneas también es un dolor:

 #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 conviertas 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 statement de expresión de GCC. (EDITAR: Aunque puedes usar un operador de coma ... lo pasaste por alto ... Pero podría ser aún 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 una prioridad menor que < . Entonces 0xFF < 42 se evalúa primero.

Características macro

  • Macro es preprocesado
  • Sin verificación de tipo
  • Aumenta la duración del código
  • El uso de macro puede provocar efectos secundarios
  • La velocidad de ejecución es más rápida
  • Antes de comstackr, el nombre de macro se reemplaza por valor de macro
  • Útil cuando el código pequeño aparece muchas veces
  • Macro no comprueba errores de comstackción

Características de la función :

  • La función está comstackda
  • La comprobación de tipo está completa
  • La duración del código permanece igual
  • Sin efectos secundarios
  • La velocidad de ejecución es más lenta
  • Durante la llamada a la función, se realiza la transferencia de control
  • Útil cuando el código grande aparece muchas veces
  • Comprobaciones de función Comstack errores

Ejemplo 1:

 #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; } 

Ejemplo 2:

 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; } 

No se repite ningún tipo de comprobación de los parámetros y el código que puede conducir a la inflamación del código. La macro syntax también puede dar lugar a una serie de casos de bordes extraños donde los puntos y coma o el orden de precedencia pueden obstaculizar el camino. Aquí hay un enlace que muestra algunos mal macro

En caso de duda, use funciones (o funciones en línea).

Sin embargo, las respuestas aquí en su mayoría explican los problemas con las macros, en lugar de tener una visión simple de que las macros son malas porque son posibles accidentes tontos.
Puedes ser consciente de las trampas y aprender a evitarlas. Luego use macros solo cuando haya una buena razón para hacerlo.

Existen ciertos casos excepcionales en los que el uso de macros presenta ventajas, como por ejemplo:

  • Funciones genéricas, como se indica a continuación, puede tener una macro que se puede usar en diferentes tipos de argumentos de entrada.
  • El número variable de argumentos se puede asignar a diferentes funciones en lugar de usar los va_args de va_args
    por ejemplo: https://stackoverflow.com/a/24837037/432509 .
  • Opcionalmente, pueden incluir información local, como cadenas de depuración:
    ( __FILE__ , __LINE__ , __func__ ). compruebe las condiciones previas / posteriores, assert al fallar, o incluso afirmaciones estáticas para que el código no se compile en el uso inadecuado (principalmente útil para comstackciones de depuración).
  • Inspeccione args de entrada, puede hacer pruebas en args de entrada, como verificar su tipo, tamaño, comprobar que los miembros de struct estén presentes antes de la conversión
    (puede ser útil para tipos polimórficos) .
    O comprueba que una matriz cumple alguna condición de longitud.
    ver: https://stackoverflow.com/a/29926435/432509
  • Si bien es cierto que las funciones sí lo hacen, C también forzará valores (ints / floats, por ejemplo). En casos raros, esto puede ser problemático. Es posible escribir macros que son más exigentes que una función sobre sus args de entrada. ver: https://stackoverflow.com/a/25988779/432509
  • Su uso como envoltorios para funciones, en algunos casos es posible que desee evitar repetirlo, por ejemplo … func(FOO, "FOO"); , podría definir una macro que expanda la cadena para usted func_wrapper(FOO);
  • Cuando desee manipular variables en el ámbito local de la persona que llama, pasar el puntero a un puntero funciona normalmente, pero en algunos casos es menos problemático utilizar una macro todavía.
    (las asignaciones a variables múltiples, para operaciones por píxel, es un ejemplo de que puede preferir una macro sobre una función … aunque todavía depende mucho del contexto, ya que las funciones en inline pueden ser una opción) .

Es cierto que algunos de ellos se basan en extensiones de comstackdores que no son estándar C. Lo que significa que puede terminar con menos código portátil, o tener que ifdef en ellos, por lo que solo se aprovechan cuando el comstackdor es compatible.


Evitar la creación de instancias de múltiples argumentos

Teniendo en cuenta esto, ya que es una de las causas más comunes de errores en las macros (por ejemplo, en x++ , donde una macro puede incrementarse varias veces) .

es posible escribir macros que eviten los efectos secundarios con la creación de instancias múltiples de argumentos.

C11 Genérico

Si desea tener una macro square que funcione con varios tipos y soporte C11, podría 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)) 

Expresiones de statement

Esta es una extensión de comstackdor compatible con GCC, Clang, EKOPath e Intel C ++ (pero no con MSVC) ;

 #define square(a_) __extension__ ({ \ typeof(a_) a = (a_); \ (a * a); }) 

Por lo tanto, la desventaja de las macros es que debes saber usarlas para empezar, y que no son compatibles de manera general.

Un beneficio es, en este caso, puede usar la misma función square para muchos tipos diferentes.

Una desventaja 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 se puede establecer un punto de interrupción dentro de una macro como se puede con las funciones.

Agregando a esta respuesta …

Las macros se sustituyen directamente en el progtwig por el preprocesador (ya que básicamente son directivas de 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 llamar y para devolver resultados, y esta sobrecarga puede evitarse mediante el uso de macros.

Además, las macros tienen algunas herramientas especiales que pueden ayudar con la portabilidad del progtwig 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 la progtwigción. Y ambas macroinstrucciones y funciones pueden usarse dependiendo de las circunstancias.

Las funciones sí escriben verificación. Esto te da una capa adicional de seguridad.

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 pueden.

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 encontrado que pasa alguna prueba (definida por el usuario). Una razón por la que podría querer hacer esto es explotar otras funciones estándar de la biblioteca: 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 por macro en lugar de 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 (enunciado no pasable vs. función pasable).