Uso normal de macros en lenguaje C

prefacio

Este artículo describe algunos usos normales de las macros en C/C++. Y alguna tecnología increíble que definitivamente no quieres que tus hijos conozcan.

1 Conocimiento relevante

Una macro es una regla de reemplazo procesada por el preprocesador antes de la compilación. Solo realiza el reemplazo de cadenas y no tiene sentido de valor. Hay dos estilos de macros, uno es similar a los objetos:

#define identifier replacement-list

Aquí, el identificador es el nombre de la macro y la lista de reemplazo es una secuencia de uno o más tokens. Todas las apariciones de identificador en programas subsiguientes se expandirán a la lista de reemplazo.
La otra es una macro de estilo de función, que es como una "metafunción durante el preprocesamiento":

#define identifier(a1, a2, ... an) replacement-list

Aquí, cada ai representa el nombre de un parámetro macro. Si la macro se usa en el siguiente programa y se proporciona un argumento apropiado, se expandirá a una lista de reemplazo, donde cada aparición del parámetro de macro se reemplazará por el argumento de macro proporcionado por el usuario.
Si hemos aprendido lenguaje C, solemos utilizar macros como esta:

int bigarray[MAXN];

De esta forma, durante el período de preprocesamiento, MAXN se reemplazará con 10001, lo que significa que el código anterior se convierte después del preprocesamiento

int bigarray[10001];

Use la opción -E de gcc para ver los resultados del preprocesamiento, y hay mejores formas, que se presentarán más adelante.

Las macros en forma de funciones se usan más comúnmente:

#ifndef min
# define min(x,y) ({
      
       \
    typeof(x) _x = (x);     \
    typeof(y) _y = (y);     \
    (void) (&_x == &_y);    \
    _x < _y ? _x : _y; })
#endif

#ifndef max
# define max(x,y) ({
      
       \
    typeof(x) _x = (x);     \
    typeof(y) _y = (y);     \
    (void) (&_x == &_y);    \
    _x > _y ? _x : _y; })
#endif

#ifndef swap
#define swap(x,y) do {
      
       \
    typeof(x) __tmp = (x); \
    (x) = (y); (y) = __tmp; \
} while(0)
#endif

La función de do ... while (0) en la macro de intercambio es combinar varias declaraciones en una sola. Si no hay do {...} while (0), la siguiente oración tendrá problemas:

if (x > y)
    swap(x, y);

Si no hay do {…} while (0), solo se ejecutará typeof(x) __tmp = (x); cuando la condición de if sea verdadera, y las demás se ejecutarán bajo cualquier condición.

En el entorno de GCC, si existe un alto requerimiento de eficiencia, se pueden requerir las siguientes dos macros:

#ifndef likely
# define likely(x)              __builtin_expect(!!(x), 1)
#endif

#ifndef unlikely
# define unlikely(x)            __builtin_expect(!!(x), 0)
#endif

ikely(x) le dice al compilador que es más probable que x sea cierto, y el compilador puede optimizar la predicción de salto en consecuencia.

Las macros más utilizadas incluyen las siguientes:

#ifndef container_of
/**
 * container_of - cast a member of a structure out to
 *   the containing structure
 * @ptr:        the pointer to the member.
 * @type:       the type of the container struct this is embedded in.
 * @member:     the name of the member within the struct.
 *
 */
# define container_of(ptr, type, member) ({
      
                           \
    const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
    (type *)( (char *)__mptr - offsetof(type,member) );})

Su utilidad ha sido claramente indicada en las notas.

2 Reglas para macros

En caso de que lo hayas olvidado, la forma funcional de la macro se ve así:

#define identifier(a1, a2, ... an) replacement-list

identificador (...) se reemplaza por lista de reemplazo, y los parámetros reales en él se colocarán en las posiciones correspondientes de acuerdo con la definición de la macro.
Si el parámetro real es una macro, primero expanda el parámetro real del identificador, y luego expanda el identificador (...) de la definición de macro actual, a menos que se encuentre con la regla 3.
En la lista de reemplazo, el formato como # a1 se reemplaza por la cadena a1.
El nombre ## a1 en la lista de reemplazo se conectará a namea1 por el preprocesador, donde a1 es un parámetro real. El valor actual
aparece en el identificador definido de la lista de reemplazo, deja de expandirse.

3 Imprime cómo se ve la macro después de la expansión

Observe atentamente las reglas de la macro, es fácil entender la macro que convierte la apariencia de la expansión de la macro en una cadena para imprimir:

#define PP_STRINGIZE(text) PP_STRINGIZE_I(text)
#define PP_STRINGIZE_I(text) #text

Después de eso, puede usar esta macro para observar cómo se ve una macro después de la expansión:

puts(PP_STRINGIZE(min(1, 2)));

4 Operaciones lógicas usando macros

En lenguaje C, consideramos 0 como verdadero y distinto de cero como falso. Pero la mayoría de las veces hemos definido verdadero como 1 y falso como 0. Se necesita algo de esfuerzo para convertir el valor en verdadero/falso. La siguiente macro convierte valores enteros de 0 a 256 a verdadero/falso. Debido a que muchos códigos se repiten, para ahorrar espacio, no todos se enumeran aquí. De hecho, no escribí línea por línea desde el teclado, sino que escribí la mayor parte del código con la función de grabación de macros del editor.

#define PP_BOOL(x) PP_BOOL_I(x)
#define PP_BOOL_I(x) PP_BOOL_ ## x

#define PP_BOOL_0 0
#define PP_BOOL_1 1
#define PP_BOOL_2 1
#define PP_BOOL_3 1
...
#define PP_BOOL_256 1

Use el siguiente código para verificar esta macro. Tenga en cuenta que la macro es procesada por el preprocesador, no intente pasarle una variable que represente un cierto valor, la macro solo puede realizar un reemplazo de cadena simple.

puts(PP_STRINGIZE(PP_BOOL(100)));
puts(PP_STRINGIZE(PP_BOOL(0)));

Sobre la base de PP_BOOL, se vuelve muy simple definir algunas operaciones lógicas:

/* bitand */
#define PP_BITAND(x, y) PP_BITAND_I(x, y)
#define PP_BITAND_I(x, y) PP_BITAND_ ## x ## y

#define PP_BITAND_00 0
#define PP_BITAND_01 0
#define PP_BITAND_10 0
#define PP_BITAND_11 1

/* bitnor */
#define PP_BITNOR(x, y) PP_BITNOR_I(x, y)
#define PP_BITNOR_I(x, y) PP_BITNOR_ ## x ## y

#define PP_BITNOR_00 1
#define PP_BITNOR_01 0
#define PP_BITNOR_10 0
#define PP_BITNOR_11 0

/* bitor */
#define PP_BITOR(x, y) PP_BITOR_I(x, y)
#define PP_BITOR_I(x, y) PP_BITOR_ ## x ## y

#define PP_BITOR_00 0
#define PP_BITOR_01 1
#define PP_BITOR_10 1
#define PP_BITOR_11 1

/* bitxor */
#define PP_BITXOR(x, y) PP_BITXOR_I(x, y)
#define PP_BITXOR_I(x, y) PP_BITXOR_ ## x ## y

#define PP_BITXOR_00 0
#define PP_BITXOR_01 1
#define PP_BITXOR_10 1
#define PP_BITXOR_11 0

El parámetro pasado a la macro anterior puede ser un valor en lugar de un simple 0/1, que debe convertirse con PP_BOOL:

#define PP_COMPL(x) PP_COMPL_I(x)
#define PP_COMPL_I(x) PP_COMPL_ ## x

#define PP_COMPL_0 1
#define PP_COMPL_1 0

#define PP_AND(p, q) PP_BITAND(PP_BOOL(p), PP_BOOL(q))
#define PP_NOR(p, q) PP_BITNOR(PP_BOOL(p), PP_BOOL(q))
#define PP_NOR(p, q) PP_BITNOR(PP_BOOL(p), PP_BOOL(q))
#define PP_NOT(x) PP_COMPL(PP_BOOL(x))
#define PP_OR(p, q) PP_BITOR(PP_BOOL(p), PP_BOOL(q))
#define PP_XOR(p, q) PP_BITXOR(PP_BOOL(p), PP_BOOL(q))

5 estructura de datos

Tuple es una tupla en algunos lenguajes y las macros también pueden implementar esta estructura.

Primero, se necesita una macro, y las dos macros se expanden y se unen:

#define PP_CAT(a, b) PP_CAT_I(a, b)
#define PP_CAT_I(a, b) a ## b

Lo siguiente define la expansión de tuplas. Se omiten muchos contenidos similares en el medio.

#define PP_REM(...) __VA_ARGS__

#define PP_TUPLE_REM(size) PP_TUPLE_REM_I(size)
#define PP_TUPLE_REM_I(size) PP_TUPLE_REM_ ## size

#define PP_TUPLE_REM_0()
#define PP_TUPLE_REM_1(e0) e0
#define PP_TUPLE_REM_2(e0, e1) e0, e1
#define PP_TUPLE_REM_3(e0, e1, e2) e0, e1, e2
#define PP_TUPLE_REM_4(e0, e1, e2, e3) e0, e1, e2, e3
#define PP_TUPLE_REM_5(e0, e1, e2, e3, e4) e0, e1, e2, e3, e4
...
#defieni PP_TUPLE_REM_64 ...

Defina otra macro que coma los primeros n elementos de una tupla.

# define PP_EAT(...)

#define PP_TUPLE_EAT(size) PP_EAT

#define PP_TUPLE_EAT_1(e0)
#define PP_TUPLE_EAT_2(e0, e1)
#define PP_TUPLE_EAT_3(e0, e1, e2)
#define PP_TUPLE_EAT_4(e0, e1, e2, e3)
#define PP_TUPLE_EAT_5(e0, e1, e2, e3, e4)
...

Por el contrario, insertar un elemento también es muy sencillo:

#define PP_TUPLE_INSERT(tuple, i, elem) \
       PP_ARRAY_TO_TUPLE(PP_ARRAY_INSERT(PP_TUPLE_TO_ARRAY(tuple), i, elem)) \

#define PP_TUPLE_INSERT_D(d, tuple, i, elem) \
       PP_ARRAY_TO_TUPLE(PP_ARRAY_INSERT_D(d, PP_TUPLE_TO_ARRAY(tuple), i, elem)) \

Bueno, no parece muy obvio, como puedes ver, esta macro hace referencia a otra estructura de datos ARRAY. De hecho, con tupla, se pueden definir muchas estructuras de datos, como una matriz y una lista, e incluso un árbol. La implementación de estas estructuras de datos requiere mucho trabajo repetitivo. Los lectores interesados ​​pueden leer el preprocesador de boost.

6 Lógica de control

Primero implemente la declaración if:

#define PP_IIF(bit, t, f) PP_IIF_I(bit, t, f)
#define PP_IIF_I(bit, t, f) PP_IIF_ ## bit(t, f)

#define PP_IIF_0(t, f) f
#define PP_IIF_1(t, f) t

#define PP_IF(cond, t, f) PP_IIF(PP_BOOL(cond), t, f)

#define PP_EXPR_IIF(bit, expr) PP_EXPR_IIF_I(bit, expr)
#define PP_EXPR_IIF_I(bit, expr) PP_EXPR_IIF_ ## bit(expr)

#define PP_EXPR_IIF_0(expr)
#define PP_EXPR_IIF_1(expr) expr

#define PP_EXPR_IF(cond, expr) PP_EXPR_IIF(PP_BOOL(cond), expr)

Un ciclo while puede parecer más útil, donde PP_AUTO_REC siempre se expande a 1.

#define PP_WHILE PP_CAT(PP_WHILE_, PP_AUTO_REC(PP_WHILE_P, 256))

#define PP_WHILE_P(n) PP_BITAND(PP_CAT(PP_WHILE_CHECK_, PP_WHILE_ ## n(PP_WHILE_F, PP_NIL, PP_NIL)), PP_CAT(PP_LIST_FOLD_LEFT_CHECK_, PP_LIST_FOLD_LEFT_ ## n(PP_NIL, PP_NIL, PP_NIL)))

#define PP_WHILE_F(d, _) 0

#define PP_WHILE_257(p, o, s) PP_ERROR(0x0001)

#define PP_WHILE_CHECK_PP_NIL 1

#define PP_WHILE_CHECK_PP_WHILE_1(p, o, s) 0
#define PP_WHILE_CHECK_PP_WHILE_2(p, o, s) 0
#define PP_WHILE_CHECK_PP_WHILE_3(p, o, s) 0
#define PP_WHILE_CHECK_PP_WHILE_4(p, o, s) 0
#define PP_WHILE_CHECK_PP_WHILE_5(p, o, s) 0
...

#define PP_WHILE_1(p, o, s) PP_WHILE_1_C(PP_BOOL(p(2, s)), p, o, s)
#define PP_WHILE_2(p, o, s) PP_WHILE_2_C(PP_BOOL(p(3, s)), p, o, s)
#define PP_WHILE_3(p, o, s) PP_WHILE_3_C(PP_BOOL(p(4, s)), p, o, s)
#define PP_WHILE_4(p, o, s) PP_WHILE_4_C(PP_BOOL(p(5, s)), p, o, s)
#define PP_WHILE_5(p, o, s) PP_WHILE_5_C(PP_BOOL(p(6, s)), p, o, s)
...

#define PP_WHILE_1_C(c, p, o, s) PP_IIF(c, PP_WHILE_2, s PP_TUPLE_EAT_3)(p, o, PP_IIF(c, o, PP_NIL PP_TUPLE_EAT_2)(2, s))
#define PP_WHILE_2_C(c, p, o, s) PP_IIF(c, PP_WHILE_3, s PP_TUPLE_EAT_3)(p, o, PP_IIF(c, o, PP_NIL PP_TUPLE_EAT_2)(3, s))
#define PP_WHILE_3_C(c, p, o, s) PP_IIF(c, PP_WHILE_4, s PP_TUPLE_EAT_3)(p, o, PP_IIF(c, o, PP_NIL PP_TUPLE_EAT_2)(4, s))
#define PP_WHILE_4_C(c, p, o, s) PP_IIF(c, PP_WHILE_5, s PP_TUPLE_EAT_3)(p, o, PP_IIF(c, o, PP_NIL PP_TUPLE_EAT_2)(5, s))
#define PP_WHILE_5_C(c, p, o, s) PP_IIF(c, PP_WHILE_6, s PP_TUPLE_EAT_3)(p, o, PP_IIF(c, o, PP_NIL PP_TUPLE_EAT_2)(6, s))
#define PP_WHILE_6_C(c, p, o, s) PP_IIF(c, PP_WHILE_7, s PP_TUPLE_EAT_3)(p, o, PP_IIF(c, o, PP_NIL PP_TUPLE_EAT_2)(7, s))
#define PP_WHILE_7_C(c, p, o, s) PP_IIF(c, PP_WHILE_8, s PP_TUPLE_EAT_3)(p, o, PP_IIF(c, o, PP_NIL PP_TUPLE_EAT_2)(8, s))
...

7 impulso/preprocesador

Boost implementa muchas macros convenientes, que los lectores pueden leer por sí mismos.

Los siguientes son algunos códigos que usan impulso. Las macros reemplazan mucho trabajo repetitivo.

#include <boost/preprocessor/arithmetic/add.hpp>
#include <boost/preprocessor/control/while.hpp>
#include <boost/preprocessor/tuple/elem.hpp>

#define PRED(n, state) BOOST_PP_TUPLE_ELEM(2, 1, state)

#define OP(d, state) \
   OP_D( \
      d, \
      BOOST_PP_TUPLE_ELEM(2, 0, state), \
      BOOST_PP_TUPLE_ELEM(2, 1, state) \
   ) \
   /**/

#define OP_D(d, res, c) \
   ( \
      BOOST_PP_ADD_D( \
     d, \
     res, \
     BOOST_PP_DEC(c) \
      ), \
      BOOST_PP_DEC(c) \
   ) \
   /**/

#define SUMMATION(n) \
   BOOST_PP_TUPLE_ELEM( \
      2, 0, \
      BOOST_PP_WHILE(PRED, OP, (n, n)) \
   ) \
   /**/

SUMMATION(3) // expands to 6
SUMMATION(4) // expands to 10

También es posible generar resultados cada ciclo:

#include <boost/preprocessor/arithmetic/inc.hpp>
#include <boost/preprocessor/comparison/not_equal.hpp>
#include <boost/preprocessor/repetition/for.hpp>
#include <boost/preprocessor/tuple/elem.hpp>

#define PRED(r, state) \
   BOOST_PP_NOT_EQUAL( \
      BOOST_PP_TUPLE_ELEM(2, 0, state), \
      BOOST_PP_INC(BOOST_PP_TUPLE_ELEM(2, 1, state)) \
   ) \
   /**/

#define OP(r, state) \
   ( \
      BOOST_PP_INC(BOOST_PP_TUPLE_ELEM(2, 0, state)), \
      BOOST_PP_TUPLE_ELEM(2, 1, state) \
   ) \
   /**/

#define MACRO(r, state) BOOST_PP_TUPLE_ELEM(2, 0, state)

BOOST_PP_FOR((5, 10), PRED, OP, MACRO) // expands to 5 6 7 8 9 10

Con el fin de ayudarlo a aprender el lenguaje C/C++ de manera fácil y eficiente, compartiré con usted los recursos que he recopilado, comenzando desde lo más básico, ¡para ayudarlo a superar los obstáculos en el camino hacia el aprendizaje del lenguaje C! Agregue el grupo Q 214574728 para obtenerlo, y también pueden aprender y comunicarse juntos

vídeo de enseñanza

Supongo que te gusta

Origin blog.csdn.net/C214574728/article/details/127588704
Recomendado
Clasificación