Pregunta

En nuestro código heredado, así como en nuestro código moderno, utilizamos macros para realizar soluciones ingeniosas como generaciones de código, etc. Y utilizamos tanto el # como el ## operadores.

Tengo curiosidad por saber cómo otros desarrolladores usan macros para hacer cosas geniales, si es que las usan.

¿Fue útil?

Solución

En C, es común definir macros que hacen algunas cosas obteniendo el argumento literal, y al mismo tiempo definir funciones para poder obtener la dirección de forma transparente.

// could evaluate at compile time if __builtin_sin gets
// special treatment by the compiler
#define sin(x) __builtin_sin(x)

// parentheses avoid substitution by the macro
double (sin)(double arg) {
    return sin(arg); // uses the macro
}

int main() {
    // uses the macro
    printf("%f\n", sin(3.14));

    // uses the function
    double (*x)(double) = &sin;

    // uses the function
    printf("%f\n", (sin)(3.14));
}

Otros consejos

La macro más genial es: afirmar, incluir guardias, __FILE__, __LINE__.
Evite usar otra macro en su código.

EDITAR:
Use macros solo cuando no tenga una solución legal sin ellas.

También existe el lenguaje X Macro que puede ser útil para DRY y la generación de código simple:

Uno define en un encabezado gen.x un tipo de tabla usando una macro aún no definida :

/** 1st arg is type , 2nd is field name , 3rd is initial value , 4th is help */
GENX( int , "y" , 1 , "number of ..." );
GENX( float , "z" , 6.3 , "this value sets ..." );
GENX( std::string , "name" , "myname" , "name of ..." );

Luego puede usarlo en diferentes lugares definiéndolo para cada #incluir con una definición generalmente diferente:

class X
{
public :

     void setDefaults()
     {
#define GENX( type , member , value , help )\
         member = value ;
#include "gen.x"
#undef GENX
     }

     void help( std::ostream & o )
     {
#define GENX( type , member , value , help )\
          o << #member << " : " << help << '\n' ;
#include "gen.x"
#undef GENX
     }

private :

#define GENX( type , member , value , help )\
     type member ;
#include "gen.x"
#undef GENX
}

Puede echar un vistazo a Boost.Preprocessor para encontrar muchos usos interesantes del preprocesador ...

SHOW () para depuración:

#define SHOW(X) cout << # X " = " << (X) << endl

La doble evaluación para expandir el truco de los argumentos: (por ejemplo, use el número de línea real y no " __ LINE __ " ;.)

    /* Use CONCATENATE_AGAIN to expand the arguments to CONCATENATE */
#define CONCATENATE(      x,y)  CONCATENATE_AGAIN(x,y)
#define CONCATENATE_AGAIN(x,y)  x ## y

Afirmaciones estáticas en tiempo de compilación.
Ej .:

#define CONCATENATE_4(      a,b,c,d)  CONCATENATE_4_AGAIN(a,b,c,d)
#define CONCATENATE_4_AGAIN(a,b,c,d)  a ## b ## c ## d

    /* Creates a typedef that's legal/illegal depending on EXPRESSION.       *
     * Note that IDENTIFIER_TEXT is limited to "[a-zA-Z0-9_]*".              *
     * (This may be replaced by static_assert() in future revisions of C++.) */
#define STATIC_ASSERT( EXPRESSION, IDENTIFIER_TEXT)                     \
  typedef char CONCATENATE_4( static_assert____,      IDENTIFIER_TEXT,  \
                              ____failed_at_line____, __LINE__ )        \
            [ (EXPRESSION) ? 1 : -1 ]

Utilizado a través de:

typedef  int32_t  int4;

STATIC_ASSERT( sizeof(int4) == 4, sizeof_int4_equal_4 );

Inicializando una instancia de la clase CodeLocation: (Almacenando Archivo / Línea / Función desde el punto de invocación - esto * SOLO * se puede hacer con una macro o accediendo directamente a las macros __FILE __ / __ LINE __ / etc en el punto fuente. )

        /* Note:  Windows may have __FUNCTION__.  C99 defines __func__. */
#define CURRENT_CODE_LOCATION()  \
           CodeLocation( __PRETTY_FUNCTION__, __FILE__, __LINE__ )

Posteriormente utilizado por las macros MESSAGE / WARN / FAIL como un mecanismo conveniente de impresión de ubicación de origen. Por ejemplo:

#define WARN_IF_NAN(X)                                      \
  do                                                        \
  {                                                         \
    if ( isnan(X) != 0 )                                    \
      WARN( # X " is NaN (Floating Point NOT-A-NUMBER)" );  \
    if ( isinf(X) != 0 )                                    \
      WARN( # X " is INF (Floating Point INFINITY)" );      \
  } while ( false )

Afirmar / A menos que las macros. Puede pasar cualquier token, incluidos operadores como '==', a través de una macro. Entonces construcciones como:

ASSERT( foo, ==, bar )

O

UNLESS( foo, >=, 0, value=0; return false; );

Son legales. Afirmar / A menos que las macros puedan agregar automáticamente todo tipo de información útil como CodeLocation, apilar rastros o lanzar excepciones / coredumping / salir con gracia.


Hacer errno más simple:

#define ERRNO_FORMAT  "errno= %d (\"%s\")"
#define ERRNO_ARGS    errno, strerror(errno)
#define ERRNO_STREAM  "errno= " << errno << " (\"" << strerror(errno) << "\") "

Por ejemplo. printf (" Error de apertura. " ERRNO_FORMAT, ERRNO_ARGS);

Uno de mis trucos favoritos es una forma de pasar un número variable de argumentos a las macros, para luego ser utilizado al llamar a funciones tipo printf, por ejemplo. Para hacer esto, especifico que la macro tiene solo un parámetro y lo uso en el cuerpo de la macro sin (), pero le paso todos los parámetros a la macro en ((y)), por lo que la lista parece un argumento único. Por ejemplo,

#define TRACE( allargs) do { printf allargs; } while ( 0)
...
TRACE(( "%s %s\n", "Help", "me"));

El registro es un lugar donde las macros se usan particularmente a menudo:

#define LOG(log) \
  if (!log.enabled()) {} \
  else log.getStream() << __FILE__ << "@" << __LINE__ << ": "


log_t errorlog;
...

LOG(errorlog) << "This doesn't look good:" << somedata;

Le doy crédito a Sean Barrett por este divertido:

#ifndef blah
    #define blah(x) // something fun
    #include __FILE__
    #undef blah
#endif

#ifndef blah
    #define blah(x) // something else that is also fun
    #include __FILE__
    #undef blah
#endif

#ifdef blah
    blah(foo)
    blah(bar)
#endif

Una forma hacky de conseguir que el preprocesador genere código para usted en base a una estructura de nivel superior que puede expresar a través de macros.

El lugar principal donde uso las macros es en mi propio marco de prueba. Por ejemplo, cuando quiero afirmar que se debe lanzar algún código, uso esta macro:

#define MUST_THROW( expr )                       
  try {                                
    (expr);                              
    (myth_suite_).Fail( #expr +                    
            std::string( " should throw but didn't" ) );  
  }                                  
  catch( ... ) {                            
  }                                  

Y úsalo así:

MUST_THROW( some_bogus_stuff() );
MUST_THROW( more_bogus_stuff() );

El único otro lugar donde los uso es en las declaraciones de clase. Tengo una macro:

#define CANNOT_COPY( cls )              \
  private:                              \
    cls( const cls & );                 \
    void operator=( const cls & )       \

que uso para especificar que una clase no se puede copiar (o asignar):

class BankAccount {

    CANNOT_COPY( BankAccount );
    ....
};

esto no hace nada especial, pero llama la atención de las personas y se puede buscar fácilmente.

Para el código incrustado, un buen truco de embeddedgurus.com le permite manejar valores binarios:

B8(01010101) // 85
B16(10101010,01010101) // 43,605
B32(10000000,11111111,10101010,01010101) // 2,164,238,93

Esto logra objetivos similares a los de la respuesta anterior de @Ferruccio sobre BOOST_BINARY, aunque un poco expandido.

Aquí está el código (copiar y pegar, no probado, ver enlace para más detalles)

// Internal Macros
#define HEX__(n) 0x##n##LU
#define B8__(x) ((x&0x0000000FLU)?1:0) \
  +((x&0x000000F0LU)?2:0) \
  +((x&0x00000F00LU)?4:0) \
  +((x&0x0000F000LU)?8:0) \
  +((x&0x000F0000LU)?16:0) \
  +((x&0x00F00000LU)?32:0) \
  +((x&0x0F000000LU)?64:0) \
  +((x&0xF0000000LU)?128:0)

// User-visible Macros
#define B8(d) ((unsigned char)B8__(HEX__(d)))
#define B16(dmsb,dlsb) (((unsigned short)B8(dmsb)<<8) + B8(dlsb))
#define B32(dmsb,db2,db3,dlsb) \
  (((unsigned long)B8(dmsb)<<24) \
  + ((unsigned long)B8(db2)<<16) \
  + ((unsigned long)B8(db3)<<8) \
  + B8(dlsb))

Me gustan las macros. ¡Muy divertido al depurar!

A menudo envuelvo cosas como la sonda de depuración en una macro simple que permite que se compile a partir de versiones de lanzamiento:

#ifdef DEBUG
#define D(s) do { s; } while(0)
#else
#define D(s) do {/**/} while(0)
#endif

El uso posterior suele ser algo así como:

D(printf("level %d, condition %s\n", level, condition));

El idioma do {} while (0) está ahí para evitar problemas que pueden resultar de hacer accidentalmente el uso de D (...) el único contenido de Un condicional o bucle. Después de todo, no desea que un código como este signifique algo incorrecto:

for(i=1;i<10;++i) D(printf("x[%d]=%f\n",i,x[i]));
SomeReallyExpensiveFunction(x);

Si pudiera hacer que ese caso arroje un error, lo haría, pero el preprocesador tendría que ser un compilador completo para decir que la macro D () era el único contenido de un cuerpo de bucle .

También soy un gran admirador de las afirmaciones en tiempo de compilación. Mi formulación es ligeramente diferente, pero no tiene ventajas reales sobre otras que he visto. La clave es formar un typedef con un nombre único que arroje un error si la condición afirmada es falsa, y no de otra manera. En cassert.h tenemos:

/*! \brief Compile-time assertion.
 *
 *  Note that the cassert() macro generates no code, and hence need not
 *  be restricted to debug builds.  It does have the side-effect of
 *  declaring a type name with typedef.  For this reason, a unique
 *  number or string of legal identifier characters must be included
 *  with each invocation to avoid the attempt to redeclare a type.
 *
 *  A failed assertion will attempt to define a type that is an array
 *  of -1 integers, which will throw an error in any standards
 *  compliant compiler. The exact error is implementation defined, but
 *  since the defined type name includes the string "ASSERTION" it
 *  should trigger curiosity enough to lead the user to the assertion
 *  itself.
 *
 *  Because a typedef is used, cassert() may be used inside a function,
 *  class or struct definition as well as at file scope.
 */
#define cassert(x,i) typedef int ASSERTION_##i[(x)?1:-1]

Y en algún archivo fuente, en cualquier lugar un typedef sería legal:

#include "cassert.h"
...
cassert(sizeof(struct foo)==14, foo1);
...

El mensaje de error resultante a menudo es oscuro, pero contendrá el fragmento de identificador que permite descubrir la línea ofensiva por la fuerza bruta.

He sido culpable de usar el preprocesador en lugares donde escribir una utilidad de generación de código podría haber sido la respuesta preferida, al igual que el código en otra respuesta que generó muchas placas de calderas basadas en las partes únicas de un miembro de enumeración nombre. Esto es especialmente útil cuando se escribe mucho pegamento de envío de mensajes para compilar en C.

Literales de estructura con valores predeterminados (que no son cero), utilizando macros variables C99

struct Example {
   int from;
   int to;
   const char *name;
}

#define EXAMPLE(...) ((struct Example){.from=0, .to=INT_MAX, .name="", __VA_ARGS__})

usando EJEMPLO (.name = " test ") usa los valores predeterminados, excepto la anulación explícita de name . Este sombreado con menciones posteriores del mismo miembro está bien definido en el estándar.

Uno puede simplificar las cosas repetitivas para ie. listas de enumeraciones

enum {
  kOneEnum,
  kTwoEnum,
  kThreeEnum,
  kFourEnum
};

... y luego hacer un caso de cambio sobre una forma estructurada

#define TEST( _v ) \
    case k ## _v ## Enum: \
      CallFunction ## _v(); \
      break;

switch (c) {
    TEST( One   );
    TEST( Two   );
    TEST( Three );
    TEST( Four  );
}

Nota: Claro que esto podría hacerse con una matriz de puntero de función, pero esto se abre para un poco más de flexibilidad para agregar parámetros y también usar las expansiones de cadena con el hash único.

... o para probar cadenas para obtener el valor de enumeración correcto

int value = -1;
char *str = getstr();

#define TEST( _v ) \
    if (!strcmp(# _v, str)) \
        value = k ## _v ## Enum

TEST( One   );
TEST( Two   );
TEST( Three );
TEST( Four  );

Puede usar macros para definir la misma funcionalidad con diferentes tipos de datos. Por ejemplo:

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <string.h>

#define DEFINE_BITS_STR(name, type)               \
char *bits_str_##name(type value)                 \
{                                                 \
    int len = sizeof(type) * CHAR_BIT;            \
    char *result;                                 \
    type n;                                       \
    int i;                                        \
                                                  \
    result = (char *)calloc(len+1, sizeof(type)); \
    if(result == NULL)                            \
        return NULL;                              \
                                                  \
    memset(result, '0', len);                     \
    result[len] = 0x00;                           \
                                                  \
    n = value;                                    \
    i = len;                                      \
    while(n)                                      \
    {                                             \
        if(n & 1)                                 \
            result[i] = '1';                      \
                                                  \
        n >>= 1;                                  \
        --i;                                      \
    }                                             \
                                                  \
    return result;                                \
}

DEFINE_BITS_STR(uchar, unsigned char)
DEFINE_BITS_STR(uint, unsigned int)
DEFINE_BITS_STR(int, unsigned int)

int main()
{
    unsigned char value1 = 134;
    unsigned int value2 = 232899;
    int value3 = 255;
    char *ret;

    ret = bits_str_uchar(value1);
    printf("%d: %s\n", value1, ret);

    ret = bits_str_uint(value2);
    printf("%d: %s\n", value2, ret);

    ret = bits_str_int(value3);
    printf("%d: %s\n", value3, ret);

    return 1;
}

En este ejemplo define tres funciones ( bits_str_uchar () , bits_str_uint () , bits_str_int () ) que manejan tres tipos de datos diferentes ( unsigned char , unsigned int , int ). Sin embargo, todos devuelven una cadena que contiene los bits del valor pasado.

Cuando implementa un servidor COM, debe ocuparse de todas las excepciones que su código pueda generar, ya que dejar una excepción a través del límite del método COM a menudo bloquea la aplicación que realiza la llamada.

Los corchetes de métodos son útiles para esto. Hay un soporte de apertura que es una macro que contiene " try " y un corchete de cierre que contiene un conjunto de '' captura '', envolviendo excepciones en ErrorInfo y produciendo HRESULT.

Del proyecto CrashRpt, necesita truco para ampliar las macros y define:

#define WIDEN2(x) L ## x 
#define WIDEN(x) WIDEN2(x)
std::wstring BuildDate = std::wstring(WIDEN(__DATE__)) + L" " + WIDEN(__TIME__);

La mayoría (¿todos?) Los marcos de prueba de unidad C ++ se basan en macros. Utilizamos UnitTest ++ . Compruébalo para ver todo tipo de macros elegantes.

La BOOST_BINARY realiza algunas engañar el truco del preprocesador para dar a C ++ la capacidad de expresar constantes numéricas en binario. Sin embargo, está limitado a 0-255.

Las macros de utilidad pthreads son IMHO particularmente impresionantes.

Cuando trabajo en grandes estructuras anidadas de c / c ++ como la utilizada para 3GPP RRC / NBAP / RNSAP, sigo este truco para que el código se vea limpio.

struct leve1_1
{
  int data;

  struct level2
  {
    int data;

    struct level3
    {
      int data;
    } level_3_data;

  } level_2_data;

} level_1_data;

level_1_data.data = 100;

#define LEVEL_2 leve1_1_data.level_2_data
LEVEL_2.data = 200;

#define LEVEL_3 LEVEL_2.level_3_data
LEVEL_3.data = 300;

#undef LEVEL_2
#undef LEVEL_3

Esto facilitará la vida durante el tiempo de mantenimiento ... también en tiempo de diseño y el código será legible.

Convirtiéndolos en una construcción del lenguaje para mejorar la seguridad de escritura y la capacidad de depuración.

void _zero_or_die(int v, const char* filename, int line)
{
    if (v != 0)
    {
       fprintf(stderr, "error %s:%d\n", filename, line);
       exit(1);
    }
}

#define ZERO_OR_DIE_ for (int _i=1; _i == 1; _zero_or_die(_i, __FILE__, __LINE__)) _i=



ZERO_OR_DIE_   pipe(fd);
ZERO_OR_DIE_   close(0);
ZERO_OR_DIE_   sigaction(SIGSEGV, &sigact, NULL);
ZERO_OR_DIE_   pthread_mutex_lock(&mt);
ZERO_OR_DIE_   pthread_create(&pt, NULL, func, NULL);

En los microcontroladores es común depurar el código con UART, ya que los puntos de interrupción del hardware tienen muchos inconvenientes.

Esta es una macro simple que ha demostrado ser muy útil:

#define DEBUG_OUT(value) sprintf(uartTxBuf, "%s = 0x%04X\n", #value, value);\
                         puts_UART((uint16_t *) uartTxBuf)

Ejemplo de uso:

for (i=0; i < 4; i++)
{
    DEBUG_OUT(i);
    DEBUG_OUT(i % 3);
}

Transmisión recibida:

i = 0x0000
i % 3 = 0x0000
i = 0x0001
i % 3 = 0x0001
i = 0x0002
i % 3 = 0x0002
i = 0x0003
i % 3 = 0x0000

Sí, es crudo e inseguro. Solo se aplica hasta que el error está aislado, por lo que esta macro no hace daño.

A menudo uso esto. Tengo un encabezado debug.h definido de la siguiente manera:

#ifndef DEBUG_H
#define DEBUG_H
    #ifdef DEBUG
    #define debuf if(1)
    #else
    #define debug if(0)
    #endif
#endif

y luego:

debug {
   printf("message from debug!");
}

si desea obtener el mensaje " mensaje de depuración! " , compile con:

gcc -D DEBUG foo.c

De lo contrario, no pasa nada. Gcc es un compilador muy inteligente. Si DEBUG no está definido, el if (0) (código muerto) generado se eliminará de su código con algunas optimizaciones activadas.

Todavía puedes hacer más:

debug 
{
   pritnf("I'm in debug mode!\n");
} 
else 
{
  printf("I'm not in debug mode\n");
}

Hace algunos días vi que el lenguaje de programación D también proporciona una función muy similar.

Si piensa lo anterior sin contexto, puede definir piensa como

#define in_debug if(1)
#define not_debug else

Y luego

in_debug {
  printf("I'm in debug mode!");
}
not_debug {
  printf("Not in debug mode!");
}

En macros, es muy fácil controlar el flujo porque es solo sustitución de texto. Aquí hay un ejemplo con un bucle for:

#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;
} 
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top