Macro vs función en C
A menudo veo 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 frente a una función?
Las macros son propensas a errores porque dependen de 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, puedes 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 con 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, lo que produce errores de sintaxis extraños o errores de tiempo de ejecución que debes expandir para comprenderlos (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 un comportamiento polimórfico, puede resultar difícil evitar el polimorfismo involuntario. 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.
Funciones de macros :
- La macro está preprocesada
- Sin verificación de tipo
- La longitud del código aumenta
- El uso de macro puede provocar efectos secundarios
- La velocidad de ejecución es más rápida
- Antes de que el nombre de la macro de compilación se reemplace por el valor de la macro
- Útil cuando aparece código pequeño muchas veces
- La macro no comprueba los errores de compilación
Características de la función :
- La función está compilada
- Se realiza la verificación de tipo
- La longitud del código sigue siendo la misma
- Sin efectos secundarios
- La velocidad de ejecución es más lenta
- Durante la llamada de función, se produce la transferencia de control.
- Útil cuando aparece código grande muchas veces
- Comprobaciones de funciones Errores de compilación
Los efectos secundarios son importantes. He aquí 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 complicado:
#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 expresión única:
int foo(int *a, int *b){
side_effect0();
side_effect1();
return a[0] + b[0];
}
No puedo hacer eso en una macro a menos que use las expresiones de declaración de GCC . (EDITAR: Sin embargo, puedes usar un operador de coma... lo pasé por alto... Pero aún así 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.
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 es posible que se produzcan accidentes tontos.
Puede ser consciente de los peligros y aprender a evitarlos. Luego utilice macros sólo cuando haya una buena razón para hacerlo.
Hay ciertos casos excepcionales en los que el uso de macros presenta ventajas, entre las que se incluyen:
- Las funciones genéricas, como se indica a continuación, pueden tener una macro que se puede usar en diferentes tipos de argumentos de entrada.
va_args
Un número variable de argumentos se puede asignar a diferentes funciones en lugar de usar C.
por ejemplo: https://stackoverflow.com/a/24837037/432509 .- Opcionalmente, pueden incluir información local, como cadenas de depuración:
(__FILE__
,__LINE__
,__func__
). verifique las condiciones previas y posteriores,assert
en caso de falla o incluso afirmaciones estáticas para que el código no se compile en caso de uso inadecuado (principalmente útil para compilaciones de depuración). - Inspeccione los argumentos de entrada. Puede realizar pruebas en los argumentos de entrada, como verificar su tipo, tamaño de, verificar que
struct
los miembros estén presentes antes de la conversión
(puede ser útil para tipos polimórficos) .
O verifique que una matriz cumpla alguna condición de longitud.
ver: https://stackoverflow.com/a/29926435/432509 - Si bien se observa que las funciones realizan verificación de tipos, C también forzará los valores (ints/floats, por ejemplo). En casos raros, esto puede resultar problemático. Es posible escribir macros que sean más exigentes que una función sobre sus argumentos de entrada. ver: https://stackoverflow.com/a/25988779/432509
- Su uso como envoltorios para funciones, en algunos casos es posible que desee evitar repetirse, por ejemplo...
func(FOO, "FOO");
, podría definir una macro que expanda la cadena por usted.func_wrapper(FOO);
- Cuando desea manipular variables en el ámbito local de la persona que llama, pasar un puntero a otro funciona bien normalmente, pero en algunos casos aún es menos problemático usar una macro.
(Las asignaciones a múltiples variables, para operaciones por píxel, son un ejemplo: es posible que prefiera una macro a una función... aunque todavía depende mucho del contexto, ya que lasinline
funciones pueden ser una opción) .
Es cierto que algunos de estos dependen de extensiones del compilador que no son el estándar C. Lo que significa que puede terminar con un código menos portátil o tener que ifdef
incluirlo, por lo que solo se aprovechan cuando el compilador lo admite.
Evitar la creación de instancias de múltiples argumentos
Tenga en cuenta esto ya que es una de las causas más comunes de errores en las macros (al pasar, x++
por ejemplo, cuando una macro puede incrementarse varias veces) .
Es posible escribir macros que eviten efectos secundarios con múltiples instancias de argumentos.
C11 Genérico
Si desea tener square
una macro que funcione con varios tipos y sea compatible con 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))
Expresiones de declaración
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); })
Una ventaja es que, en este caso, puedes utilizar la misma square
función para muchos tipos diferentes.
Entonces, la desventaja de las macros es que debes estar consciente de los peligros, y algunas de las extensiones útiles no son tan compatibles con todos los compiladores de C.