¿Por qué las macros de preprocesador son malas y cuáles son las alternativas?

14 minutos de lectura

¿Por que las macros de preprocesador son malas y cuales
usuario1849534

Siempre he preguntado esto pero nunca he recibido una respuesta realmente buena; Creo que casi cualquier programador antes incluso de escribir el primer “Hello World” se había encontrado con una frase como “nunca se deben usar las macros”, “las macros son malas” y así sucesivamente, mi pregunta es: ¿por qué? Con el nuevo C++11, ¿existe una alternativa real después de tantos años?

La parte fácil es sobre macros como #pragmaque son específicos de la plataforma y del compilador, y la mayoría de las veces tienen fallas graves como #pragma once eso es propenso a errores en al menos 2 situaciones importantes: el mismo nombre en diferentes rutas y con algunas configuraciones de red y sistemas de archivos.

Pero, en general, ¿qué pasa con las macros y las alternativas a su uso?

  • #pragma no es macro

    – FooF

    26 de diciembre de 2012 a las 13:47

  • ¿Directiva de preprocesador @foof?

    – usuario1849534

    26 de diciembre de 2012 a las 13:51

  • @user1849534: Sí, eso es lo que es… y los consejos sobre macros no se refieren a #pragma.

    – Ben Voigt

    26 de diciembre de 2012 a las 13:52

  • Puedes hacer mucho con constexpr, inline funciones, y templatespero boost.preprocessor y chaos mostrar que las macros tienen su lugar. Sin mencionar las macros de configuración para diferentes compiladores, plataformas, etc.

    – Brandón

    26 de diciembre de 2012 a las 13:52

  • Consulte también “¿Son malas todas las macros?”

    – Brad Larson

    23 de enero de 2013 a las 18:56

¿Por que las macros de preprocesador son malas y cuales
esteras petersson

Las macros son como cualquier otra herramienta: un martillo utilizado en un asesinato no es malo porque es un martillo. Es malo 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” (explicaré cada una más adelante y sugeriré alternativas):

  1. No puede depurar macros.
  2. La expansión macro puede provocar efectos secundarios extraños.
  3. 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 macros donde no los quería, y esto generalmente genera mensajes de error extraños.
  4. Las macros pueden afectar cosas de las que no te das 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 pueden “ver” a qué se traduce la macro. Así que en realidad no sabes lo que está pasando.

Reemplazo: Utilizar enum o const T

Para las macros “similares a funciones”, debido a que el depurador funciona en un nivel “por línea de origen donde se encuentre”, su macro actuará como una declaración única, sin importar si es una declaración o cien. Hace que sea difícil darse cuenta de lo que 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 expansiones macro 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 quería el programador. Si fuera una función, estaría bien hacer x++, y x solo se incrementaría una vez.

Otro ejemplo es “if else” en macros, digamos que tenemos esto:

#define safe_divide(res, x, y)   if (y != 0) res = x/y;

y luego

if (something) safe_divide(b, a, x);
else printf("Something is not set...");

En realidad, 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 algo de 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 recibes y dónde buscas un error? [assuming you have completely forgotten – or didn’t even know about – the begin macro that lives in some header file that someone else wrote? [and even more fun if you included that macro before the include – you’d be drowning in strange errors that makes absolutely no sense when you look at the code itself.

Replacement: Well there isn’t so much as a replacement as a “rule” – only use uppercase names for macros, and never use all uppercase names for other things.

4) Macros have effects you don’t realize

Take this function:

#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();

}

Now, without looking at the macro, you would think that begin is a function, which shouldn’t affect x.

This sort of thing, and I’ve seen much more complex examples, can REALLY mess up your day!

Replacement: Either don’t use a macro to set x, or pass x in as an argument.

There are times when using macros is definitely beneficial. One example is to wrap a function with macros to pass on file/line information:

#define malloc(x) my_debug_malloc(x, __FILE__, __LINE__)
#define free(x)  my_debug_free(x, __FILE__, __LINE__)

Now we can use my_debug_malloc as the regular malloc in the code, but it has extra arguments, so when it comes to the end and we scan the “which memory elements hasn’t been freed”, we can print where the allocation was made so the programmer can track down the leak.

[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 los intentos y propósitos, así es como deberíamos considerarlo. Así que haciendo x++ * x++ actualizará x dos veces, lo que no está definido y probablemente conducirá a diferentes valores en diferentes sistemas, y diferente valor de resultado en x así como.

  • los if else los problemas se pueden resolver envolviendo el cuerpo de la macro dentro do { ... } while(0). Esto se comporta como cabría esperar con respecto a if y for y otros problemas de flujo de control potencialmente riesgosos. Pero sí, una función real suele ser una mejor solución. #define macro(arg1) do { int x = func(arg1); func2(x0); } while(0)

    – Aaron McDaid

    10/10/2013 a las 22:10


  • @AaronMcDaid: Sí, hay algunas soluciones que solucionan algunos de los problemas que se exponen en estas macros. El objetivo de mi publicación no era mostrar cómo hacer bien las macros, sino “lo fácil que es equivocarse en las macros”, donde hay una buena alternativa. Dicho esto, hay cosas que las macros resuelven muy fácilmente, y hay momentos en que las macros también son lo correcto.

    – Mats Peterson

    11 de octubre de 2013 a las 6:10

  • En el punto 3, los errores ya no son realmente un problema. Los compiladores modernos como Clang dirán algo como note: expanded from macro 'begin' y mostrar donde begin se define.

    – kirbyfan64sos

    5 de marzo de 2015 a las 20:50

  • Las macros son difíciles de traducir a otros idiomas.

    –Marco van de Voort

    1 de marzo de 2016 a las 8:29

  • @FrancescoDondi: stackoverflow.com/questions/4176328/… (bastante abajo en esa respuesta, habla de i++ * i++ y tal.

    – Mats Peterson

    9 de septiembre de 2017 a las 6:03

1647540977 269 ¿Por que las macros de preprocesador son malas y cuales
utnapistim

El dicho “las macros son malas” generalmente se refiere al uso de #define, no de #pragma.

En concreto, la expresión se refiere a estos dos casos:

  • definiendo números mágicos como macros

  • uso de macros para reemplazar expresiones

con el nuevo C++ 11 hay una alternativa real después de tantos años?

Sí, para los elementos de la lista anterior (los números mágicos deben definirse con const/constexpr y las expresiones deben definirse con [normal/inline/template/inline template] funciones

Estos son algunos de los problemas que se presentan al definir números mágicos como macros y reemplazar expresiones con macros (en lugar de definir funciones para evaluar esas expresiones):

  • al definir macros para números mágicos, el compilador no retiene información de tipo para los valores definidos. Esto puede causar advertencias (y errores) de compilación y confundir a las personas que depuran el código.

  • al definir macros en lugar de funciones, los programadores que usan ese código esperan que funcionen como funciones y no es así.

Considere este código:

#define max(a, b) ( ((a) > (b)) ? (a) : (b) )

int a = 5;
int b = 4;

int c = max(++a, b);

Esperaría que a y c fueran 6 después de la asignación a c (como lo haría, usando std::max en lugar de la macro). En su lugar, el código realiza:

int c = ( ((++a) ? (b)) ? (++a) : (b) ); // after this, c = a = 7

Además de esto, las macros no admiten espacios de nombres, lo que significa que definir macros en su código limitará el código del cliente en cuanto a los nombres que pueden usar.

Esto significa que si define la macro anterior (para max), ya no podrá #include <algorithm> en cualquiera de los siguientes códigos, a menos que escriba explícitamente:

#ifdef max
#undef max
#endif
#include <algorithm>

Tener macros en lugar de variables/funciones también significa que no puede tomar su dirección:

  • si una macro como constante se evalúa como un número mágico, no puede pasarla por dirección

  • para una macro como función, no puede usarla como predicado ni tomar la dirección de la función ni tratarla como un funtor.

Editar: como ejemplo, la alternativa correcta a la #define max encima:

template<typename T>
inline T max(const T& a, const T& b)
{
    return a > b ? a : b;
}

Esto hace todo lo que hace la macro, con una limitación: si los tipos de argumentos son diferentes, la versión de la plantilla lo obliga a ser explícito (lo que en realidad conduce a un código más seguro y explícito):

int a = 0;
double b = 1.;
max(a, b);

Si este máximo se define como una macro, el código se compilará (con una advertencia).

Si este máximo se define como una función de plantilla, el compilador señalará la ambigüedad y tendrá que decir: max<int>(a, b) o max<double>(a, b) (y así declarar explícitamente su intención).

  • No tiene que ser específico de c++11; simplemente puede usar funciones para reemplazar el uso de macros como expresiones y [static] const / constexpr para reemplazar el uso de macros como constantes.

    – utnapistim

    26 de diciembre de 2012 a las 14:23

  • Incluso C99 permite el uso de const int someconstant = 437;, y se puede usar casi de todas las formas en que se usaría una macro. Igualmente para funciones pequeñas. Hay algunas cosas en las que puede escribir algo como una macro que no funcionará en una expresión regular en C (podría hacer algo que promedie una matriz de cualquier tipo de número, lo que C no puede hacer, pero C++ tiene plantillas para eso). Si bien C ++ 11 agrega algunas cosas más que “no necesita macros para esto”, en su mayoría ya está resuelto en C / C ++ anterior.

    – Mats Peterson

    26 de diciembre de 2012 a las 14:39

  • Hacer un incremento previo al pasar un argumento es una práctica de codificación terrible. Y cualquiera que codifique en C/C++ debería no suponga que una llamada similar a una función no es una macro.

    – StephenG – Ayuda a Ucrania

    14 de mayo de 2018 a las 9:29

  • Muchas implementaciones ponen voluntariamente entre paréntesis los identificadores max y min si van seguidos de un paréntesis izquierdo. Pero no debes definir tales macros …

    – LF

    12 de julio de 2019 a las 5:24

1647540977 186 ¿Por que las macros de preprocesador son malas y cuales
faazon

Un problema común es este:

#define DIV(a,b) a / b

printf("25 / (3+2) = %d", DIV(25,3+2));

Imprimirá 10, no 5, porque el preprocesador lo expandirá de esta manera:

printf("25 / (3+2) = %d", 25 / 3 + 2);

Esta versión es más segura:

#define DIV(a,b) (a) / (b)

  • ejemplo interesante, básicamente son solo tokens sin semántica

    – usuario1849534

    26 de diciembre de 2012 a las 13:53

  • Si. Se expanden de la forma en que se asignan a la macro. los DIV la macro se puede reescribir con un par de () alrededor b.

    – faazon

    26 de diciembre de 2012 a las 13:56

  • Te refieres a #define DIV(a,b)no #define DIV (a,b)que es muy diferente.

    – rico

    26 de diciembre de 2012 a las 15:43

  • #define DIV(a,b) (a) / (b) No es suficientemente bueno; como práctica general, siempre agregue los corchetes más externos, así: #define DIV(a,b) ( (a) / (b) )

    – PJ Trail

    13 de junio de 2016 a las 21:36

Las macros son valiosas especialmente para crear código genérico (los parámetros de la macro pueden ser cualquier cosa), a veces con parámetros.

Además, este código se coloca (es decir, se inserta) en el punto en que se usa la macro.

OTOH, se pueden lograr resultados similares con:

  • funciones sobrecargadas (diferentes tipos de parámetros)

  • plantillas, en C++ (tipos y valores de parámetros genéricos)

  • funciones en línea (coloque el código donde se llaman, en lugar de saltar a una definición de un solo punto; sin embargo, esto es más bien una recomendación para el compilador).

editar: en cuanto a por qué la macro es mala:

1) no hay verificación de tipo de los argumentos (no tienen tipo), por lo que se pueden usar mal fácilmente 2) a veces se expanden en un código muy complejo, que puede ser difícil de identificar y comprender en el archivo preprocesado 3) es fácil cometer errores -código propenso en macros, como:

#define MULTIPLY(a,b) a*b

y luego llamar

MULTIPLY(2+3,4+5)

que se expande en

2+3*4+5 (y no en: (2+3)*(4+5)).

Para tener esto último, debe definir:

#define MULTIPLY(a,b) ((a)*(b))

No creo que haya nada de malo en usar definiciones de preprocesador o macros como las llamas.

Son un concepto de (meta) lenguaje que se encuentra en c/c++ y, como cualquier otra herramienta, pueden facilitarle la vida si sabe lo que está haciendo. El problema con las macros es que se procesan antes que su código c/c++ y generan código nuevo que puede ser defectuoso y causar errores de compilación que son casi obvios. En el lado positivo, pueden ayudarlo a mantener su código limpio y ahorrarle mucho tipeo si se usan correctamente, por lo que se trata de una preferencia personal.

  • Además, como lo señalaron otras respuestas, las definiciones de preprocesador mal diseñadas pueden producir código con sintaxis válida pero con un significado semántico diferente, lo que significa que el compilador no se quejará y usted introdujo un error en su código que será aún más difícil de encontrar.

    – Sandi Hrvic

    26 de diciembre de 2012 a las 14:04

1647540978 506 ¿Por que las macros de preprocesador son malas y cuales
indiogarg

Las macros en C/C++ pueden servir como una herramienta importante para el control de versiones. El mismo código se puede entregar a dos clientes con una configuración menor de macros. yo uso cosas como

#define IBM_AS_CLIENT
#ifdef IBM_AS_CLIENT 
  #define SOME_VALUE1 X
  #define SOME_VALUE2 Y
#else
  #define SOME_VALUE1 P
  #define SOME_VALUE2 Q
#endif

Este tipo de funcionalidad no es posible tan fácilmente sin macros. Las macros son en realidad una excelente herramienta de administración de configuración de software y no solo una forma de crear accesos directos para reutilizar el código. La definición de funciones con el fin de reutilizarlas en macros definitivamente puede crear problemas.

  • Además, como lo señalaron otras respuestas, las definiciones de preprocesador mal diseñadas pueden producir código con sintaxis válida pero con un significado semántico diferente, lo que significa que el compilador no se quejará y usted introdujo un error en su código que será aún más difícil de encontrar.

    – Sandi Hrvic

    26 de diciembre de 2012 a las 14:04

1647540978 506 ¿Por que las macros de preprocesador son malas y cuales
indiogarg

Las macros de preprocesador no son malas cuando se usan para fines previstos como:

  • Crear diferentes versiones del mismo software usando el tipo de construcciones #ifdef, por ejemplo, la versión de ventanas para diferentes regiones.
  • Para definir valores relacionados con las pruebas de código.

Alternativas-
Uno puede usar algún tipo de archivos de configuración en formato ini, xml, json para propósitos similares. Pero usarlos tendrá efectos de tiempo de ejecución en el código que una macro de preprocesador puede evitar.

  • desde C++17 constexpr if + un archivo de encabezado que contiene variables constexpr “config” puede reemplazar las #ifdef’s.

    – Enhex

    19 sep 2019 a las 17:06

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad