What are some tricks I can use with macros? [close

2019-01-16 02:38发布

问题:

In our legacy code, as well as our modern code, we use macros to perform nifty solutions like code generations, etc. And we make use of both the # and ## operators.

I am curious how other developers use macros to do cool things, if they use them at all.

回答1:

In C, it's common to define macros that do some stuff getting the verbatim argument, and at the same time define functions to be able to get the address of it transparently.

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


回答2:

Coolest macro is: assert, include guards, __FILE__, __LINE__.
Avoid using other macro in your code.

EDIT:
Use macros only when you don't have legal solution w/o them.



回答3:

There is also the X Macro idiom which can be useful for DRY and simple code generation :

One defines in a header gen.x a kind of table using a not yet defined macro :

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

Then he can use it in different places defining it for each #include with a usually different definition :

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
}


回答4:

You can have a look at Boost.Preprocessor to find lot's of interesting uses of the preprocessor...



回答5:

SHOW() for debugging:

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

The double-evaluation to expand the arguments trick: (E.g. Use the actual line number and not "__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

Static compile-time assertions.
E.g.:

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

Used via:

typedef  int32_t  int4;

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

Initializing an instance of class CodeLocation: (Storing File/Line/Function from the point of invocation -- this can *ONLY* be done with a macro or by directly accessing the __FILE__/__LINE__/etc macros at the source point.)

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

Subsequently used by MESSAGE/WARN/FAIL macros as a convenient source-location printing mechanism. For example:

#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/Unless macros. You can pass any token, including operators like '==', through a macro. So constructs like:

ASSERT( foo, ==, bar )

Or

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

Are legal. Assert/Unless macros can automatically add all sorts the nice useful info like CodeLocation, stack traces, or throwing exceptions / coredumping / exiting gracefully.


Making errno simplier:

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

E.g. printf( "Open failed. " ERRNO_FORMAT, ERRNO_ARGS );



回答6:

One of my favorite tricks is a way to pass variable number of arguments to macros, to be later used in calling printf-like functions for example. To do this, I specify that the macro has only one parameter and use it in the body of the macro without (), but pass all the parameters to the macro in (( and )), so the list looks like a single argument. For example,

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


回答7:

Logging is one place where macros are particulary often used:

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


log_t errorlog;
...

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


回答8:

I credit Sean Barrett for this fun one:

#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

A hacky way to get the preprocessor to generate code for you based on some higher level structure that you can express through macros.



回答9:

The main place I use macros is in my own testing framework. For example, when I want to assert that some code must throw, I use this macro:

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

And use it like this:

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

The only other place I use them is in class declarations. I have a macro:

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

which I use to specify that a class cannot be copied (or assigned):

class BankAccount {

    CANNOT_COPY( BankAccount );
    ....
};

this doesn't do anything special but draws peoples attention and can easily be searched for.



回答10:

For embedded code, a nice trick from embeddedgurus.com enables you to handle binary values:

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

This achieve similar goals as the previous response from @Ferruccio about BOOST_BINARY, although a bit expanded.

Here's the code (copy'n pasted, not tested, see link for more 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))

I like macros. So much fun when debugging !



回答11:

I often wrap things like debug sonar in a simple macro that allows it to be compiled out of release builds:

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

Usage later is typically something like:

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

The do{}while(0) idiom is there to avoid issues that might result from accidentally making a usage of D(...) the only content of a conditional or loop. You don't want code like this to mean the wrong thing, after all:

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

If I could make that case throw an error, I would, but the preprocessor would have to be a full compiler itself to tell that the D() macro was the sole content of a loop body.

I'm also a big fan of compile-time assertions. My formulation is slightly different, but has no real advantages over others I've seen. The key is to form a uniquely named typedef that throws an error if the asserted condition is false, and not otherwise. In cassert.h we have:

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

And in some source file, anywhere a typedef would be legal:

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

The resulting error message is often obscure, but will contain the fragment of identifier enabling the offending line to be discovered by brute force.

I've been guilty of using the preprocessor in places where writing a code generation utility might have been the preferred answer, much like the code in another answer that generated lots of boiler-plate based on the unique parts of an enum member's name. That is especially handy when writing a lot of message-dispatch glue to be compiled in C.



回答12:

One can simplify repetitive things for ie. enum lists

enum {
  kOneEnum,
  kTwoEnum,
  kThreeEnum,
  kFourEnum
};

...and later do a switch case over a structured way

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

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

Note: Sure this could be done with a function pointer array but this opens for a little more flexibilities to add parameters and also use the string expansions with the single hash.

...or to test on strings to get the right enum value

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


回答13:

Struct literals with default values (that are not zero), using C99 variadic macros

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

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

using EXAMPLE(.name="test") uses the default values, except for the explicit override of name. This shadowing with later mentions of the same member is well-defined in the standard.



回答14:

You can use macros to define the same functionality with different data types. For example:

#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 this example defines three functions (bits_str_uchar(), bits_str_uint(), bits_str_int()) that handle three different data types (unsigned char, unsigned int, int). However, all return a string that contains the bits of the value passed.



回答15:

When you implement a COM server, you have to take care of all exceptions your code could possibly throw - letting an exception through COM method boundary will often crash the calling application.

Methods brackets are useful for this. There's an opening bracket which is a macro containing "try" and a closing bracket that contains a set of "catch"es, wrapping of exceptions into ErrorInfo and producing HRESULTs.



回答16:

From the CrashRpt project , need trick to widen macros and defines:

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


回答17:

Most (all?) C++ Unit Testing frameworks are built upon macros. We use UnitTest++. Check it out to see all sorts of fancy macros.



回答18:

The BOOST_BINARY macro performs some clevel pre-processor trickery to give C++ the ability to express numeric constants in binary. It is limited to 0-255 however.



回答19:

The pthreads utility macros are particularily impressive IMHO.



回答20:

When i work on huge c/c++ nested structures like the one used for 3GPP RRC/NBAP/RNSAP, i follow this trick to make the code look clean.

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

This will make life easier during maintenance time..also in design time and code will be readable.



回答21:

Converting them to a construct of the language to improve type safety and debugging ability.



回答22:

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


回答23:

On micro controllers it is common to debug code using UART, as hardware breakpoints have many drawbacks.

This is a simple macro that has proven very useful:

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

Usage example:

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

Recieved stream:

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

Yes, it's crude and unsafe. It's only applied until the bug is isolated, so this macro does no harm.



回答24:

Often I use this. I have a debug.h header define as the following:

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

and then:

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

if you want to get "message from debug!" message, compile with:

gcc -D DEBUG foo.c

Otherwise, nothing happens. Gcc is a very smart compiler. If DEBUG isn't defined, the generated if(0) (dead code) will be removed from your code with some optimizations on.

You still can do more:

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

Some days ago I seen the D programming language provide a feature very similar too.

If you think the above without context, you can define thinks as

#define in_debug if(1)
#define not_debug else

And then

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


回答25:

In macros, it's very easy to do control flow because it's just text substitution. Here's an example with a for loop:

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