Frage

In unserer Legacy-Code, sowie unsere modernen Code verwenden wir Makros raffinierte Lösungen wie Code Generationen zu erfüllen, etc. Und wir nutzen sowohl die # und ## Betreiber.

Ich bin gespannt, wie andere Entwickler Makros coole Dinge zu tun, zu verwenden, wenn sie sie überhaupt verwendet werden.

War es hilfreich?

Lösung

In C ist es üblich, Makros zu definieren, das einige Dinge tun, um das wörtlich Argument bekommen, und zugleich Funktionen definieren zu können, die Adresse es transparent erhalten.

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

Andere Tipps

Coolest Makro ist: behaupten, gehören Wachen, __FILE__, __LINE__
. Vermeiden Sie die Verwendung anderer Makro in Ihrem Code.

EDIT:
Verwenden von Makros nur, wenn Sie keine rechtliche Lösung haben w / o sie.

Es gibt auch die X-Makro Sprache, die für trockene und einfache Code-Generierung nützlich sein kann:

Man definiert in einem Header gen.x eine Art Tisch mit einem noch nicht definiert Makro:

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

Dann kann er es an verschiedenen Orten verwenden Sie es für jede #include mit einer in der Regel unterschiedlichen Definition definieren:

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
}

Sie können einen Blick auf Boost.Preprocessor finden viel von interessanten Anwendungen des Vorprozessors ...

SHOW () für das Debuggen:

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

Die Doppel Auswertung der Argumente Trick zu erweitern: (. Z. B. Verwenden Sie die aktuelle Zeilennummer und nicht „__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

Statische Kompilierung-Behauptungen.
Z. B:.

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

Gebraucht über:

typedef  int32_t  int4;

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

Initialisieren einer Instanz der Klasse CodeLocation: (Speichern Datei / Line / Funktion vom Punkt der Aufruf - das kann * * nur mit einem Makro oder durch den direkten Zugriff auf die __FILE __ / __ LINE __ / etc Makros am Quellpunkt erfolgen. )

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

Anschließend durch MESSAGE verwendet / WARN / FAIL-Makros als ein bequemen Quelle-Standort Druckmechanismus. Zum Beispiel:

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

Assert / Sofern Makros. Sie können eine beliebige Token passieren, einschließlich Operatoren wie ‚==‘, durch einen Makro. So konstruiert wie:

ASSERT( foo, ==, bar )

oder

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

Sind legal. Behaupten / Sofern Makros kann automatisch alle Arten die schönen nützlichen Informationen wie CodeLocation hinzufügen, Stack-Traces oder werfen Ausnahmen / Coredump / Verlassen anmutig.


Herstellung errno simplier:

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

z. printf ( "failed Öffnen". ERRNO_FORMAT, ERRNO_ARGS);

Einer meiner Lieblingstricks ist eine Möglichkeit, variable Anzahl von Argumenten zu Makros zu übergeben, die später in Aufruf printf-ähnliche Funktionen zum Beispiel verwendet werden. Dazu geben Sie ich, dass das Makro nur einen Parameter hat und verwenden Sie es im Körper des Makros ohne (), aber passieren alle Parameter an das Makro in ((und)), so dass die Liste sieht aus wie ein einziges Argument. Zum Beispiel:

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

Die Protokollierung ist ein Ort, wo Makros werden besonders häufig verwendet:

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


log_t errorlog;
...

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

I Kredit Sean Barrett für diesen Spaß ein:

#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

Ein hacky Weg, um die Prä-Prozessor-Code zu generieren für Sie auf einer höheren Ebene-Struktur basieren, die Sie durch Makros zum Ausdruck bringen können.

Der Haupt Ort, wo ich Makros ist in meinem eigenen Test-Framework. Zum Beispiel, wenn ich, dass einige Code behaupten will, werfen muss, verwende ich dieses Makro:

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

Und es wie folgt verwendet werden:

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

Der einzige andere Ort, wo ich sie benutzen, ist in Klassendeklarationen. Ich habe einen Makro:

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

, die ich verwende, um festzulegen, dass eine Klasse kann nicht kopiert werden (oder vergeben):

class BankAccount {

    CANNOT_COPY( BankAccount );
    ....
};

das tut nichts Besonderes, aber lenkt die Aufmerksamkeit Völker und leicht gesucht werden kann.

Für Embedded-Code, ein schöner Trick von embeddedgurus.com ermöglicht es Ihnen, binäre Werte zu handhaben:

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

Dies erreicht ähnliche Ziele wie die vorherige Antwort von @Ferruccio über BOOST_BINARY, wenn auch ein wenig erweitert.

Hier ist der Code (klebte copy'n, nicht getestet, siehe Link für weitere Details)

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

Ich mag Makros. So viel Spaß beim Debuggen!

Ich wickle oft Dinge wie Debug-Sonar in einem einfachen Makro, das es aus der Veröffentlichung zu erstellenden ermöglicht baut:

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

Verwendung später ist in der Regel etwas wie:

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

Das do{}while(0) Idiom gibt es Probleme zu vermeiden, die aus Versehen zu machen ist eine Nutzung D(...) den einzigen Inhalt eines bedingten oder Schleife führen könnten. Sie wollen nicht, Code wie dies die falsche Sache zu verstehen, schließlich:

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

Wenn ich kann, dass Fall macht einen Fehler aus, ich würde, aber der Prä-Prozessor würde einen vollständigen Compiler sein muß, sich zu sagen, dass der D() Makro alleiniger Inhalt eines Schleifenkörpers ist.

Ich bin auch ein großer Fan von Kompilierung-Behauptungen. Meine Formulierung ist etwas anders, aber hat keine wirklichen Vorteile gegenüber anderen, die ich je gesehen habe. Der Schlüssel ist ein eindeutig benannter typedef zu bilden, die einen Fehler aus, wenn die geltend gemachte Bedingung falsch ist, und nicht anders. In cassert.h haben wir:

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

Und in einiger Quelldatei, überall ein typedef wäre legal:

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

Die sich ergebende Fehlermeldung ist oft dunkel, sondern wird das Fragment der Identifikator ermöglicht, die fehlerhaften Zeile enthalten, mit roher Gewalt entdeckt zu werden.

Ich habe den Präprozessor der Verwendung an Orten begangen, wo ein Codegenerierungsprogramm schreiben könnte die bevorzugte Antwort gewesen sein, ähnlich wie der Code in einer anderen Antwort, die auf der Grundlage der einzigartigen Teile eines ENUM-Mitglied eine Menge Kesselplatte erzeugt Name. Das ist besonders praktisch, wenn viele Nachrichten-Versand Kleber Schreiben in C erstellt werden.

Struct Literale mit Standardwerten (die nicht Null sind), unter Verwendung von C99 variadische Makros

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

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

mit EXAMPLE(.name="test") die Standardwerte verwendet, mit Ausnahme der ausdrücklich außer Kraft gesetzt name. Diese Abschattung mit später erwähnt desselben Elemente wohldefiniert in der Norm.

Man kann sich wiederholende Dinge also vereinfachen. Aufzählungslisten

enum {
  kOneEnum,
  kTwoEnum,
  kThreeEnum,
  kFourEnum
};

... und später tun, um eine Schaltergehäuse über eine strukturierte Art und Weise

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

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

. Hinweis: sicher, dass dies mit einem Funktionszeiger Array getan werden könnte, aber dies eröffnet für ein wenig mehr Flexibilitäten Parameter hinzufügen und auch die String-Erweiterungen mit dem einzigen Hash verwenden

... oder auf Strings zu testen das Recht Enum-Wert zu erhalten

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

Sie können Makros verwenden, um die gleiche Funktionalität mit unterschiedlichen Datentypen zu definieren. Zum Beispiel:

#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 diesem Beispiel definiert drei Funktionen (bits_str_uchar(), bits_str_uint(), bits_str_int()), die drei verschiedene Datentypen (unsigned char, unsigned int, int) handhaben. Doch alle zurückgeben eine Zeichenfolge, die die Bits des Werts enthält übergeben.

Wenn Sie einen COM-Server implementieren, müssen Sie den Code kümmern uns um alle Ausnahmen möglicherweise werfen könnte -. Eine Ausnahme durch COM-Methode Grenze im Stich gelassen wird oft die rufende Anwendung abstürzen

Methoden Klammern sind nützlich für diese. Es gibt eine öffnende Klammer, die ein Makro enthält, „versuchen“ und ein Schließbügel, der einen Satz von „Fang“ es enthält, Verpackung von Ausnahmen in Errorinfo und Herstellung HRESULTs.

Aus dem CrashRpt Projekt benötigt Trick Makros zu erweitern und definiert:

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

Die meisten (alle?) C ++ Unit Testing Frameworks auf Makros erstellt werden. Wir verwenden Unittest ++ . Probieren Sie es aus, um alle Arten von fancy Makros.

Die BOOST_BINARY Makro führt einige Clevel Vorprozessor trickery macht C ++ die Fähigkeit, numerische Konstanten in binärer auszudrücken. Es beschränkt sich auf 0-255 jedoch.

Die pThreads Dienstprogramm Makros particularily beeindruckende IMHO sind.

Wenn ich auf riesigen c arbeiten / c ++ verschachtelte Strukturen wie die für 3GPP RRC / NBAP / RNSAP- verwendet, folge ich diesem Trick den Code aussehen lassen sauber.

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

Dies wird das Leben leichter macht bei der Wartung time..also in Design-Zeit und Code lesbar sein.

Rechnet man dies auf ein Konstrukt des Sprachtypen Sicherheit und Debuggen Fähigkeit zu verbessern.

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

Auf Mikrocontrollern ist es üblich, zu Debug-Code mit UART, als Hardware Breakpoints viele Nachteile haben.

Dies ist ein einfaches Makro, das sehr nützlich erwiesen hat:

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

Anwendungsbeispiel:

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

Empfing Strom:

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

Ja, es ist roh und unsicher. Es wird nur angewandt, bis der Fehler isoliert wird, so dass dieser Makro schadet nicht.

Oft verwende ich diese. Ich habe einen debug.h Header definieren wie folgt:

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

und dann:

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

Wenn Sie "message from debug!" Nachricht erhalten möchten, kompilieren mit:

gcc -D DEBUG foo.c

Ansonsten passiert nichts. Gcc ist ein sehr intelligenter Compiler. Wenn DEBUG nicht definiert ist, die erzeugte if(0) (dead-Code) wird aus dem Code mit einigen Optimierungen entfernt werden.

Sie können noch mehr tun:

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

Vor einigen Tagen habe ich die Programmiersprache D eine Funktion sehr ähnlich zu bieten gesehen.

Wenn Sie ohne Kontext die oben denken, können Sie festlegen, denkt wie

#define in_debug if(1)
#define not_debug else

Und dann

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

In Makros, es ist sehr einfach zu tut Kontrollfluss , weil es nur Textersetzung. Hier ist ein Beispiel mit einem for-Schleife:

#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;
} 
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top