Macro vs función en C

Resuelto Kyrol asked hace 12 años • 11 respuestas

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?

Kyrol avatar Feb 02 '12 05:02 Kyrol
Aceptado

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.

Chiara Coetzee avatar Feb 01 '2012 23:02 Chiara Coetzee

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
zangw avatar Nov 27 '2015 09:11 zangw

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)

xse 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 < 42se evalúa primero.

Mysticial avatar Feb 01 '2012 22:02 Mysticial

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_argsUn 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, asserten 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 structlos 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 las inlinefunciones 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 ifdefincluirlo, 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 squareuna 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 squarefunció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.

ideasman42 avatar Aug 08 '2014 18:08 ideasman42