Domanda

Nel nostro codice legacy, così come nel nostro codice moderno, utilizziamo le macro per eseguire soluzioni ingegnose come generazioni di codici, ecc. E utilizziamo sia # che ## operatori.

Sono curioso di sapere come altri sviluppatori usano le macro per fare cose interessanti, se le usano affatto.

È stato utile?

Soluzione

In C, è comune definire macro che fanno alcune cose ottenendo l'argomento testuale e allo stesso tempo definire le funzioni per essere in grado di ottenere l'indirizzo in modo trasparente.

// 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));
}

Altri suggerimenti

La macro più interessante è: asserisci, include guards, __FILE__, __LINE__.
Evita di usare altre macro nel tuo codice.

Modifica
Usa le macro solo quando non disponi di una soluzione legale senza di esse.

Esiste anche il linguaggio X Macro che può essere utile per DRY e la semplice generazione di codice:

In un'intestazione gen.x si definisce un tipo di tabella usando una macro non ancora definita :

/** 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 ..." );

Quindi può usarlo in luoghi diversi definendolo per ogni #include con una definizione solitamente diversa:

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
}

Puoi dare un'occhiata a Boost.Preprocessor per trovare molti usi interessanti del preprocessore ...

SHOW () per il debug:

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

La doppia valutazione per espandere il trucco degli argomenti: (Es. Usa il numero di riga effettivo e non " __ 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

Asserzioni statiche in fase di compilazione.
per esempio:.

#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 ]

Utilizzato tramite:

typedef  int32_t  int4;

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

Inizializzazione di un'istanza della classe CodeLocation: (Archiviazione di file / linea / funzione dal punto di invocazione - questo può * SOLO * essere eseguito con una macro o accedendo direttamente alle macro __FILE __ / __ LINE __ / etc nel punto di origine. )

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

Successivamente utilizzato dalle macro MESSAGE / WARN / FAIL come comodo meccanismo di stampa della posizione della sorgente. Ad esempio:

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

Assegna / A meno macro. Puoi passare qualsiasi token, inclusi operatori come '==', attraverso una macro. Quindi costruisce come:

ASSERT( foo, ==, bar )

o

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

Sono legali. Asserire / A meno che le macro non possano aggiungere automaticamente tutti i tipi di informazioni utili come CodeLocation, impilare tracce o lanciare eccezioni / coredumping / uscire con garbo.


Semplificazione dell'errno:

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

es. printf (" Apri fallito. " ERRNO_FORMAT, ERRNO_ARGS);

Uno dei miei trucchi preferiti è un modo per passare un numero variabile di argomenti alle macro, da utilizzare successivamente per chiamare funzioni simili a printf, ad esempio. Per fare ciò, specifica che la macro ha un solo parametro e lo uso nel corpo della macro senza (), ma passa tutti i parametri alla macro in ((e)), quindi l'elenco appare come un singolo argomento. Ad esempio,

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

La registrazione è un luogo in cui le macro vengono utilizzate in modo particolarmente frequente:

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


log_t errorlog;
...

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

Accredito Sean Barrett per questo divertente:

#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

Un modo bizzarro per convincere il preprocessore a generare codice per te basato su una struttura di livello superiore che puoi esprimere attraverso le macro.

Il posto principale in cui utilizzo le macro è nel mio framework di test. Ad esempio, quando voglio affermare che un po 'di codice deve essere lanciato, uso questa macro:

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

E usalo in questo modo:

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

L'unico altro posto in cui li uso è nelle dichiarazioni di classe. Ho una macro:

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

che utilizzo per specificare che una classe non può essere copiata (o assegnata):

class BankAccount {

    CANNOT_COPY( BankAccount );
    ....
};

questo non fa nulla di speciale ma attira l'attenzione della gente e può essere facilmente cercato.

Per il codice incorporato, un bel trucco da embeddedgurus.com consente di gestire valori binari:

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

Ciò raggiunge obiettivi simili alla precedente risposta di @Ferruccio su BOOST_BINARY, anche se un po 'ampliata.

Ecco il codice (copia e incolla, non testato, vedi link per maggiori dettagli)

// 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))

Mi piacciono le macro. Molto divertente durante il debug!

Spesso avvolgo cose come il sonar di debug in una semplice macro che gli consente di essere compilato dalle build di rilascio:

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

L'utilizzo successivo è in genere simile a:

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

Il fa {} mentre (0) idioma è lì per evitare problemi che potrebbero derivare dall'uso accidentale di D (...) l'unico contenuto di un condizionale o loop. Non vuoi che codice come questo significhi la cosa sbagliata, dopo tutto:

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

Se potessi fare in modo che il caso generi un errore, lo farei, ma il preprocessore dovrebbe essere un compilatore completo per dire che la macro D () era il solo contenuto di un corpo del ciclo .

Sono anche un grande fan delle asserzioni in fase di compilazione. La mia formulazione è leggermente diversa, ma non ha vantaggi reali rispetto agli altri che ho visto. La chiave è formare un typedef con un nome univoco che genera un errore se la condizione asserita è falsa e non diversamente. In cassert.h abbiamo:

/*! \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]

E in alcuni file sorgente, ovunque un typedef sarebbe legale:

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

Il messaggio di errore risultante è spesso oscuro, ma conterrà il frammento di identificativo che consente di scoprire la linea offensiva con la forza bruta.

Sono stato colpevole di usare il preprocessore in luoghi in cui la scrittura di un'utilità di generazione del codice avrebbe potuto essere la risposta preferita, proprio come il codice in un'altra risposta che ha generato un sacco di piastre di caldaia basate sulle parti uniche di un membro enum nome. Ciò è particolarmente utile quando si scrive molta colla per la spedizione di messaggi da compilare in C.

valori letterali Struct con valori predefiniti (diversi da zero), utilizzando le macro variadic C99

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

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

utilizzando EXAMPLE (.name = " test ") utilizza i valori predefiniti, ad eccezione della sostituzione esplicita di name . Questo oscuramento con menzioni successive dello stesso membro è ben definito nello standard.

Uno può semplificare le cose ripetitive per es. elenchi enum

enum {
  kOneEnum,
  kTwoEnum,
  kThreeEnum,
  kFourEnum
};

... e successivamente esegui una commutazione su un modo strutturato

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

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

Nota: Sicuramente questo potrebbe essere fatto con un array di puntatori a funzione ma questo si apre per un po 'più di flessibilità per aggiungere parametri e usare anche le espansioni di stringhe con il singolo hash.

... o per testare le stringhe per ottenere il giusto valore enum

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

È possibile utilizzare le macro per definire la stessa funzionalità con tipi di dati diversi. Ad esempio:

#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;
}

In questo esempio definisce tre funzioni ( bits_str_uchar () , bits_str_uint () , bits_str_int () ) che gestiscono tre diversi tipi di dati ( unsigned char , unsigned int , int ). Tuttavia, tutti restituiscono una stringa che contiene i bit del valore passato.

Quando si implementa un server COM, è necessario fare attenzione a tutte le eccezioni che il codice potrebbe eventualmente generare: lasciare un'eccezione attraverso il limite del metodo COM causerà spesso l'arresto anomalo dell'applicazione chiamante.

Le parentesi dei metodi sono utili per questo. C'è una parentesi quadra aperta che è una macro contenente " provare " e una parentesi quadra di chiusura che contiene un insieme di "catch", avvolgimento di eccezioni in ErrorInfo e produzione di HRESULT.

Dal progetto CrashRpt, è necessario un trucco per allargare le macro e definire:

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

La maggior parte (tutti?) i framework di test unit C ++ sono basati su macro. Utilizziamo UnitTest ++ . Dai un'occhiata per vedere tutti i tipi di macro fantasiose.

La macro BOOST_BINARY esegue alcune inganno trucco pre-processore per dare al C ++ la capacità di esprimere costanti numeriche in binario. Tuttavia, è limitato a 0-255.

Le macro di utilità pthreads sono IMHO particolarmente impressionanti.

Quando lavoro su enormi strutture nidificate c / c ++ come quella usata per 3GPP RRC / NBAP / RNSAP, seguo questo trucco per rendere il codice pulito.

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

Questo renderà la vita più facile durante i tempi di manutenzione .. anche in fase di progettazione e il codice sarà leggibile.

Convertendoli in un costrutto del linguaggio per migliorare la sicurezza dei tipi e la capacità di debug.

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

Su micro controller è comune eseguire il debug del codice utilizzando UART, poiché i punti di interruzione hardware presentano numerosi inconvenienti.

Questa è una macro semplice che si è rivelata molto utile:

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

Esempio di utilizzo:

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

Stream ricevuto:

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

Sì, è grezzo e non sicuro. Viene applicato solo fino a quando il bug non viene isolato, quindi questa macro non danneggia.

Spesso lo uso. Ho un'intestazione debug.h definita come la seguente:

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

e quindi:

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

se vuoi ricevere " messaggio dal debug! " , compila con:

gcc -D DEBUG foo.c

Altrimenti, non succede nulla. Gcc è un compilatore molto intelligente. Se DEBUG non è definito, il generato se (0) (codice morto) verrà rimosso dal codice con alcune ottimizzazioni attivate.

Puoi ancora fare di più:

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

Alcuni giorni fa ho visto il linguaggio di programmazione D fornire anche una funzione molto simile.

Se pensi a quanto sopra senza contesto, puoi definire thinks come

#define in_debug if(1)
#define not_debug else

E poi

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

Nelle macro, è molto facile controllare il flusso perché è solo una sostituzione del testo. Ecco un esempio con un ciclo 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;
} 
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top