una qué not macro language elif dev define c macros c-preprocessor language-design language-features

not - ¿Para qué son útiles las macros C?



if not defined c++ (18)

He escrito un poco de C , y puedo leerlo lo suficiente como para tener una idea general de lo que está haciendo, pero cada vez que me encuentro con una macro me ha lanzado por completo. Termino teniendo que recordar cuál es la macro y sustituirla en mi cabeza mientras leo. Los que he encontrado que eran intuitivos y fáciles de entender eran siempre pequeñas mini funciones, así que siempre me pregunté por qué no eran solo funciones.

Puedo entender la necesidad de definir diferentes tipos de compilación para compilaciones de depuración o multiplataforma en el preprocesador, pero la capacidad de definir sustituciones arbitrarias parece ser útil solo para hacer que un lenguaje ya difícil sea aún más difícil de entender.

¿Por qué se introdujo un preprocesador tan complejo para C ? ¿Y alguien tiene un ejemplo de usarlo que me haga entender por qué parece que todavía se usa para fines distintos a los simples si # compilaciones condicionales de estilo #debug?

Editar:

Después de haber leído una cantidad de respuestas, todavía no lo entiendo. La respuesta más común es al código en línea. Si la palabra clave en línea no lo hace, entonces tiene una buena razón para no hacerlo o la implementación necesita reparación. No entiendo por qué se necesita un mecanismo completamente diferente que signifique "realmente en línea este código" (aparte del código que se está escribiendo antes de que estuviera en línea). Tampoco entiendo la idea de que se mencionó que "si es demasiado tonto como para ponerlo en una función". Seguramente cualquier pieza de código que toma una entrada y produce una salida se pone mejor en una función. Creo que es posible que no lo obtenga porque no estoy acostumbrado a las micro optimizaciones de escribir C , pero el preprocesador se siente como una solución compleja a algunos problemas simples.


Termino teniendo que recordar cuál es la macro y sustituirla en mi cabeza mientras leo.

Eso parece reflejar mal en el nombramiento de las macros. Supongo que no tendría que emular el preprocesador si fuera una macro log_function_entry() .

Los que he encontrado que eran intuitivos y fáciles de entender eran siempre pequeñas mini funciones, así que siempre me pregunté por qué no eran solo funciones.

Por lo general, deberían serlo, a menos que necesiten operar con parámetros genéricos.

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

funcionará en cualquier tipo con un < operador.

Más que simplemente funciones, las macros le permiten realizar operaciones usando los símbolos en el archivo fuente. Eso significa que puede crear un nuevo nombre de variable o hacer referencia al archivo de origen y al número de línea en el que se encuentra la macro.

En C99, las macros también le permiten llamar a funciones variadas como printf

#define log_message(guard,format,...) / if (guard) printf("%s:%d: " format "/n", __FILE__, __LINE__,__VA_ARGS_); log_message( foo == 7, "x %d", x)

En el cual el formato funciona como printf. Si el guarda es verdadero, emite el mensaje junto con el archivo y el número de línea que imprimió el mensaje. Si fuera una llamada a función, no sabría el archivo y la línea desde la que lo llamó, y usar un vaprintf sería un poco más vaprintf .


A diferencia de las funciones normales, puede controlar el flujo (if, while, for, ...) en macros. Aquí hay un ejemplo:

#include <stdio.h> #define Loop(i,x) for(i=0; i<x; i++) int main(int argc, char *argv[]) { int i; int x = 5; Loop(i, x) { printf("%d", i); // Output: 01234 } return 0; }


Añadiré a lo que ya se dijo.

Debido a que las macros funcionan en las sustituciones de texto, te permiten hacer cosas muy útiles que no sería posible hacer mediante el uso de funciones.

Aquí algunos casos donde las macros pueden ser realmente útiles:

/* Get the number of elements in array ''A''. */ #define ARRAY_LENGTH(A) (sizeof(A) / sizeof(A[0]))

Esta es una macro muy popular y de uso frecuente. Esto es muy útil cuando, por ejemplo, necesita iterar a través de una matriz.

int main(void) { int a[] = {1, 2, 3, 4, 5}; int i; for (i = 0; i < ARRAY_LENGTH(a); ++i) { printf("a[%d] = %d/n", i, a[i]); } return 0; }

Aquí no importa si otro programador agrega cinco elementos más a a en la declinación. El bucle for siempre iterará a través de todos los elementos.

Las funciones de la biblioteca C para comparar memoria y cadenas son bastante desagradables de usar.

Usted escribe:

char *str = "Hello, world!"; if (strcmp(str, "Hello, world!") == 0) { /* ... */ }

o

char *str = "Hello, world!"; if (!strcmp(str, "Hello, world!")) { /* ... */ }

Para comprobar si str señala a "Hello, world" . Personalmente creo que estas dos soluciones se ven bastante feas y confusas ( !strcmp(...) especialmente !strcmp(...) ).

Aquí hay dos macros ordenadas que algunas personas (incluida I) usan cuando necesitan comparar cadenas o memoria usando strcmp / memcmp :

/* Compare strings */ #define STRCMP(A, o, B) (strcmp((A), (B)) o 0) /* Compare memory */ #define MEMCMP(A, o, B) (memcmp((A), (B)) o 0)

Ahora puedes escribir el código así:

char *str = "Hello, world!"; if (STRCMP(str, ==, "Hello, world!")) { /* ... */ }

¡Aquí está la intención mucho más clara!

Estos son casos donde las macros se usan para cosas que las funciones no pueden lograr. Las macros no se deben usar para reemplazar funciones, pero tienen otros buenos usos.


Además de aumentar la eficiencia y la compilación condicional, las macros se pueden usar para elevar el nivel de abstracción del código C de bajo nivel. C realmente no lo aísla de los detalles esenciales de la administración de memoria y recursos y el diseño exacto de los datos, y admite formas muy limitadas de ocultamiento de información y otros mecanismos para administrar sistemas grandes. Con las macros, ya no está limitado a usar solo las construcciones base en el lenguaje C: puede definir sus propias estructuras de datos y construcciones de codificación (¡incluidas clases y plantillas!) Mientras todavía escribe nominalmente C!

Las macros de preprocesador realmente ofrecen un lenguaje Turing-complete ejecutado en tiempo de compilación. Uno de los ejemplos impresionantes (y un poco atemorizantes) de esto está terminado en el lado de C ++: la biblioteca del preprocesador Boost usa el preprocesador C99 / C++98 para construir construcciones de programación (relativamente) seguras que luego se expanden a las declaraciones subyacentes y el código usted ingresa, ya sea C o C ++.

En la práctica, recomendaría la programación de preprocesadores como último recurso, cuando no tiene la libertad de usar construcciones de alto nivel en lenguajes más seguros. ¡Pero a veces es bueno saber qué puedes hacer si tu espalda está contra la pared y las comadrejas se están cerrando ...!


Aprovechando la manipulación del texto del preprocesador de C, se puede construir el equivalente en C de una estructura de datos polimórficos. Usando esta técnica podemos construir una caja de herramientas confiable de estructuras de datos primitivas que se pueden usar en cualquier programa C, ya que aprovechan la sintaxis C y no los detalles de una implementación en particular.

Aquí se brinda una explicación detallada sobre cómo usar macros para administrar la estructura de datos: http://multi-core-dump.blogspot.com/2010/11/interesting-use-of-c-macros-polymorphic.html


De las estupideces de la computadora :

He visto este extracto del código en muchos programas de juegos gratuitos para UNIX:

/ *
* Valores de bit.
* /
#define BIT_0 1
#define BIT_1 2
#define BIT_2 4
#define BIT_3 8
#define BIT_4 16
#define BIT_5 32
#define BIT_6 64
#define BIT_7 128
#define BIT_8 256
#define BIT_9 512
#define BIT_10 1024
#define BIT_11 2048
#define BIT_12 4096
#define BIT_13 8192
#define BIT_14 16384
#define BIT_15 32768
#define BIT_16 65536
#define BIT_17 131072
#define BIT_18 262144
#define BIT_19 524288
#define BIT_20 1048576
#define BIT_21 2097152
#define BIT_22 4194304
#define BIT_23 8388608
#define BIT_24 16777216
#define BIT_25 33554432
#define BIT_26 67108864
#define BIT_27 134217728
#define BIT_28 268435456
#define BIT_29 536870912
#define BIT_30 1073741824
#define BIT_31 2147483648

Una forma mucho más fácil de lograr esto es:

#define BIT_0 0x00000001
#define BIT_1 0x00000002
#define BIT_2 0x00000004
#define BIT_3 0x00000008
#define BIT_4 0x00000010
...
#define BIT_28 0x10000000
#define BIT_29 0x20000000
#define BIT_30 0x40000000
#define BIT_31 0x80000000

Una forma más fácil de hacerlo es dejar que el compilador haga los cálculos:

#define BIT_0 (1)
#define BIT_1 (1 << 1)
#define BIT_2 (1 << 2)
#define BIT_3 (1 << 3)
#define BIT_4 (1 << 4)
...
#define BIT_28 (1 << 28)
#define BIT_29 (1 << 29)
#define BIT_30 (1 << 30)
#define BIT_31 (1 << 31)

Pero ¿por qué tomarse tantas molestias para definir 32 constantes? El lenguaje C también tiene macros parametrizadas. Todo lo que realmente necesitas es:

#define BIT (x) (1 << (x))

De todos modos, me pregunto si el chico que escribió el código original usó una calculadora o simplemente lo calculó todo en papel.

Ese es solo un posible uso de Macros.


Es bueno para incluir código y evitar la sobrecarga de llamadas de función. Además de usarlo si quieres cambiar el comportamiento más tarde sin editar muchos lugares. No es útil para cosas complejas, pero para líneas simples de código que desea alinear, no está mal.


Este fragmento resume bastante mi punto de vista sobre el asunto, al comparar varias formas en que se usan las macros C y cómo implementarlas en D

copiado de DigitalMars.com

Cuando se inventó C , la tecnología del compilador era primitiva. La instalación de un preprocesador de macro de texto en la interfaz era una manera simple y sencilla de agregar muchas funciones potentes. El tamaño y la complejidad crecientes de los programas han ilustrado que estas características presentan muchos problemas inherentes. D no tiene un preprocesador; pero D proporciona un medio más escalable para resolver los mismos problemas.

Macros

Las macros de preprocesador agregan potentes funciones y flexibilidad a C Pero tienen un inconveniente:

  • Las macros no tienen ningún concepto de alcance; son válidos desde el punto de definición hasta el final de la fuente. Cortan una franja a través de archivos .h, código anidado, etc. Cuando se incluyen #include ''decenas de miles de líneas de definiciones de macros, resulta problemático evitar expansiones macro inadvertidas.
  • Las macros son desconocidas para el depurador. El depurador trata de depurar un programa con datos simbólicos solo al conocer las expansiones macro, no las macros en sí.
  • Las macros hacen imposible tokenizar el código fuente, ya que un cambio macro anterior puede rehacer tokens arbitrariamente.
  • La base puramente textual de las macros lleva a un uso arbitrario e incoherente, haciendo que el código con macros sea propenso a errores. (Algunos intentos de resolver esto se introdujeron con plantillas en C++ ).
  • Las macros todavía se usan para compensar los déficits en la capacidad expresiva del lenguaje, como para "envoltorios" de archivos de encabezado.

Aquí hay una enumeración de los usos comunes para las macros, y la característica correspondiente en D:

  1. Definiendo constantes literales:

    • El modo Preprocesador C

      #define VALUE 5

    • La forma D

      const int VALUE = 5;

  2. Crear una lista de valores o indicadores:

    • El modo Preprocesador C

      int flags: #define FLAG_X 0x1 #define FLAG_Y 0x2 #define FLAG_Z 0x4 ... flags |= FLAG_X;

    • La forma D

      enum FLAGS { X = 0x1, Y = 0x2, Z = 0x4 }; FLAGS flags; ... flags |= FLAGS.X;

  3. Establecer convenciones de llamada a funciones:

    • El modo Preprocesador C

      #ifndef _CRTAPI1 #define _CRTAPI1 __cdecl #endif #ifndef _CRTAPI2 #define _CRTAPI2 __cdecl #endif int _CRTAPI2 func();

    • La forma D

      Las convenciones de llamadas se pueden especificar en bloques, por lo que no es necesario cambiarlas para cada función:

      extern (Windows) { int onefunc(); int anotherfunc(); }

  4. Programación genérica simple:

    • El modo Preprocesador C

      Seleccionar qué función usar en función de la sustitución de texto:

      #ifdef UNICODE int getValueW(wchar_t *p); #define getValue getValueW #else int getValueA(char *p); #define getValue getValueA #endif

    • La forma D

      D habilita las declaraciones de símbolos que son alias de otros símbolos:

      version (UNICODE) { int getValueW(wchar[] p); alias getValueW getValue; } else { int getValueA(char[] p); alias getValueA getValue; }

Hay más ejemplos en el sitio web de DigitalMars .


Las macros permiten que alguien modifique el comportamiento del programa durante el tiempo de compilación. Considera esto:

  • Las constantes C permiten la fijación del comportamiento del programa en el momento del desarrollo
  • Las variables C permiten modificar el comportamiento del programa en el momento de la ejecución
  • Las macros C permiten modificar el comportamiento del programa en tiempo de compilación

En tiempo de compilación significa que el código no utilizado ni siquiera entrará en el binario y que el proceso de compilación puede modificar los valores, siempre que esté integrado con el macroprocesador. Ejemplo: make ARCH = arm (supone que la definición de macro de reenvío es cc -DARCH = arm)

Ejemplos simples: (a partir de glibc limits.h, define el valor más grande de largo)

#if __WORDSIZE == 64 #define LONG_MAX 9223372036854775807L #else #define LONG_MAX 2147483647L #endif

Verifica (usando #define __WORDSIZE) en tiempo de compilación si estamos compilando para 32 o 64 bits. Con una cadena de herramientas multilib, el uso de los parámetros -m32 y -m64 puede cambiar automáticamente el tamaño del bit.

(Solicitud de versión POSIX)

#define _POSIX_C_SOURCE 200809L

Solicitudes durante el tiempo de compilación POSIX 2008. La biblioteca estándar puede admitir muchos estándares (incompatibles) pero con esta definición, proporcionará los prototipos de función correctos (por ejemplo: getline (), no gets (), etc.). Si la biblioteca no es compatible con el estándar, puede dar un error # durante el tiempo de compilación, en lugar de bloquearse durante la ejecución, por ejemplo.

(ruta de acceso codificada)

#ifndef LIBRARY_PATH #define LIBRARY_PATH "/usr/lib" #endif

Define, durante el tiempo de compilación, un directorio de hardcode. Podría ser cambiado con -DLIBRARY_PATH = / home / user / lib, por ejemplo. Si fuera un const char *, ¿cómo lo configuraría durante la compilación?

(pthread.h, definiciones complejas en tiempo de compilación)

# define PTHREAD_MUTEX_INITIALIZER / { { 0, 0, 0, 0, 0, 0, { 0, 0 } } }

Es posible que se declaren grandes fragmentos de texto que, de lo contrario, no se simplificarían (siempre en tiempo de compilación). No es posible hacer esto con funciones o constantes (en tiempo de compilación).

Para evitar complicar las cosas y evitar sugerir estilos de codificación deficientes, no daré un ejemplo de código que se compila en diferentes sistemas operativos incompatibles. Utilice su sistema de compilación cruzada para eso, pero debe quedar claro que el preprocesador lo permite sin ayuda del sistema de compilación, sin interrumpir la compilación debido a interfaces ausentes.

Finalmente, piense en la importancia de la compilación condicional en los sistemas integrados, donde la velocidad y la memoria del procesador son limitadas y los sistemas muy heterogéneos.

Ahora, si pregunta, ¿es posible reemplazar todas las definiciones de macro constante y las llamadas a función con las definiciones adecuadas? La respuesta es sí, pero no hará que la necesidad de cambiar el comportamiento del programa durante la compilación desaparezca. El preprocesador aún sería necesario.


Macros ... para cuando tu & # (* $ & compilador simplemente se niega a alinear algo.

Eso debería ser un póster motivacional, ¿no?

Con toda seriedad, el abuso del preprocesador de google (es posible que vea una pregunta similar como el resultado n. ° 1). Si estoy escribiendo una macro que va más allá de la funcionalidad de assert (), generalmente trato de ver si mi compilador en realidad alinearía una función similar.

Otros argumentarán en contra de usar #if para la compilación condicional ... preferirían que:

if (RUNNING_ON_VALGRIND)

más bien que

#if RUNNING_ON_VALGRIND

.. con fines de depuración, ya que puedes ver if () pero no #if en un depurador. Luego nos sumergimos en #ifdef vs #if.

Si se trata de menos de 10 líneas de código, intenta alinearlo. Si no se puede alinear, intente optimizarlo. Si es demasiado tonto como para ser una función, crea una macro.


No vi a nadie mencionar esto, por lo que respecta a la función como macros, por ejemplo:

#define MIN(X, Y) ((X) < (Y) ? (X) : (Y))

En general, se recomienda evitar el uso de macros cuando no sea necesario, por muchas razones, siendo la legibilidad la principal preocupación. Asi que:

¿Cuándo deberías usar estos sobre una función?

Casi nunca, ya que hay una alternativa más legible que está en inline , consulte https://www.greenend.org.uk/rjk/tech/inline.html o http://www.cplusplus.com/articles/2LywvCM9/ (the el segundo enlace es una página de C ++, pero el punto es aplicable a los compiladores de c por lo que yo sé).

Ahora, la pequeña diferencia es que las macros son manejadas por el preprocesador y el compilador lo maneja en línea, pero hoy en día no hay diferencia práctica.

¿Cuándo es apropiado usar estos?

Para funciones pequeñas (dos o tres revestimientos máximos). El objetivo es obtener alguna ventaja durante el tiempo de ejecución de un programa, ya que la función como macros (y funciones en línea) son reemplazos de código realizados durante el preprocesamiento (o compilación en caso de en línea) y no son funciones reales que viven en la memoria, por lo que no hay una sobrecarga de llamada de función (más detalles en las páginas vinculadas).


Recuerde que las macros (y el preprocesador) provienen de los primeros días de C. Solían ser la ÚNICA forma de hacer ''funciones'' en línea (porque, por supuesto, en línea es una palabra clave muy reciente), y siguen siendo el única forma de FORZAR algo para ser inline.

Además, las macros son la única forma en que puede hacer trucos como insertar el archivo y la línea en constantes de cadena en tiempo de compilación.

En estos días, muchas de las cosas que las macros solían ser la única forma de hacerlo se manejan mejor a través de mecanismos más nuevos. Pero todavía tienen su lugar, de vez en cuando.


Si bien no soy un gran admirador de las macros y ya no escribo mucho C, de acuerdo con mi tarea actual, algo como esto (que obviamente podría tener algunos efectos secundarios) es conveniente:

#define MIN(X, Y) ((X) < (Y) ? (X) : (Y))

Ahora no he escrito algo así en años, pero las "funciones" de ese tipo eran un código que mantuve al principio de mi carrera. Supongo que la expansión podría considerarse conveniente.


Son un lenguaje de programación (uno más simple) en la parte superior de C, por lo que son útiles para hacer metaprogramación en tiempo de compilación ... en otras palabras, puede escribir código macro que genera el código C en menos líneas y el tiempo que tomará escribiéndolo directamente en C.

También son muy útiles para escribir expresiones de "función similar" que son "polimórficas" o "sobrecargadas"; por ejemplo, una macro máxima definida como:

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

es útil para cualquier tipo numérico; y en C no puedes escribir:

int max(int a, int b) {return a>b?a:b;} float max(float a, float b) {return a>b?a:b;} double max(double a, double b) {return a>b?a:b;} ...

incluso si quisiera, porque no puede sobrecargar funciones.

Y sin mencionar la compilación condicional y el archivo incluido (que también son parte del lenguaje de macros) ...


Teniendo en cuenta los comentarios de su pregunta, es probable que no lo aprecie del todo, es que llamar a una función puede implicar una buena cantidad de gastos generales. Es posible que los parámetros y los registros de claves deban copiarse en la pila en el camino hacia adentro, y que la pila se desenrolle en el camino de salida. Esto fue particularmente cierto con los chips Intel más antiguos. Las macros permiten que el programador mantenga la abstracción de una función (casi), pero evitó la costosa sobrecarga de una llamada a función. La palabra clave en línea es consultiva, pero el compilador puede no hacerlo siempre correctamente. La gloria y el peligro de ''C'' es que generalmente puedes doblar el compilador a tu voluntad.

En su rutina diaria, la programación de aplicaciones de este tipo de micro-optimización (evitando llamadas a funciones) generalmente es peor que inútil, pero si está escribiendo una función de tiempo crítico llamada por el kernel de un sistema operativo, entonces Puede hacer una enorme diferencia.


Una de las razones obvias es que al usar una macro, el código se expandirá en tiempo de compilación y obtendrá una pseudo llamada de función sin la sobrecarga de la llamada.

De lo contrario, también puede usarlo para constantes simbólicas, para que no tenga que editar el mismo valor en varios lugares para cambiar una cosa pequeña.


Uno de los casos en que las macros realmente brillan es cuando se genera código con ellas.

Solía ​​trabajar en un viejo sistema de C ++ que usaba un sistema de complementos con su propia forma de pasar los parámetros al plugin (usando una estructura tipo mapa personalizada). Se usaron algunas macros simples para poder manejar esta peculiaridad y nos permitieron usar clases y funciones C ++ reales con parámetros normales en los complementos sin demasiados problemas. Todo el código de pegamento generado por las macros.


Las macros le permiten deshacerse de fragmentos pegados, que no puede eliminar de ninguna otra manera.

Por ejemplo (el código real, la sintaxis del compilador VS 2010):

for each (auto entry in entries) { sciter::value item; item.set_item("DisplayName", entry.DisplayName); item.set_item("IsFolder", entry.IsFolder); item.set_item("IconPath", entry.IconPath); item.set_item("FilePath", entry.FilePath); item.set_item("LocalName", entry.LocalName); items.append(item); }

Este es el lugar donde pasa un valor de campo con el mismo nombre en un motor de script. ¿Está esta copia pegada? Sí. DisplayName se usa como una cadena para una secuencia de comandos y como un nombre de campo para el compilador. ¿Es tan malo? Sí. Si refactorizas tu código y LocalName nombre de LocalName a RelativeFolderName (como yo lo hice) y olvidas hacer lo mismo con la cadena (como lo hice), el script funcionará de una manera que no esperas (de hecho, en mi ejemplo, depende de si olvidaste cambiar el nombre del campo en un archivo de script separado, pero si el script se usa para la serialización, sería un error del 100%).

Si usa una macro para esto, no habrá espacio para el error:

for each (auto entry in entries) { #define STR_VALUE(arg) #arg #define SET_ITEM(field) item.set_item(STR_VALUE(field), entry.field) sciter::value item; SET_ITEM(DisplayName); SET_ITEM(IsFolder); SET_ITEM(IconPath); SET_ITEM(FilePath); SET_ITEM(LocalName); #undef SET_ITEM #undef STR_VALUE items.append(item); }

Desafortunadamente, esto abre una puerta para otros tipos de errores. Puede hacer un error al escribir la macro y nunca verá un código dañado, porque el compilador no muestra cómo se ve después de todo el preprocesamiento. Alguien más podría usar el mismo nombre (es por eso que "lanzo" macros lo antes posible con #undef ). Entonces, úsalo sabiamente Si ve otra forma de deshacerse del código de copiado (como las funciones), utilícelo de esa manera. Si ve que eliminar el código de copiado con macros no vale la pena, mantenga el código de copiado.