Rare cases where MACROs must be used

2019-01-23 02:30发布

问题:

Debugging macros can take a lot of time. We are much better off avoiding them except in the very rare cases when neither constants, functions nor templates can do what we want.

What are the rare cases?

回答1:

If you want actual textual replacement, that's where you use macros. Take a look at Boost.Preprocessor, it's a great way to simulate variadic templates in C++03 without repeating yourself too much.

In other words, if you want to manipulate the program code itself, use macros.

Another useful application is assert, which is defined to be a no-op when NDEBUG is not defined (usually release mode compilation).

That brings us to the next point, which is a specialization of the first one: Different code with different compilation modes, or between different compilers. If you want cross-compiler support, you can't get away without macros. Take a look at Boost in general, it needs macros all the time because of various deficiencies in various compilers it has to support.

Another important point is when you need call-site information without wanting to bug the user of your code. You have no way to automatically get that with just a function.

#define NEEDS_INFO() \
  has_info(__FILE__, __LINE__, __func__)

With a suitable declaration of has_info (and C++11/C99 __func__ or similar).



回答2:

This question doesn't appear to have a definite, closed-form answer, so I'll just give a couple of examples.

Suppose you want to print information about a given type. Type names don't exist in the compiled code, so they cannot possibly be expressed by the language itself (except for C++ extensions). Here the preprocessor must step in:

#define PRINT_TYPE_INFO(type) do { printf("sizeof(" #type ") = %zu\n", sizeof(type)); } while (false)

PRINT_TYPE_INFO(int);
PRINT_TYPE_INFO(double);

Similarly, function names are not themselves variable, so if you need to generate lots of similar names, the preprocessor helps:

#define DECLARE_SYM(name) fhandle libfoo_##name = dlsym("foo_" #name, lib);

DECLARE_SYM(init);   // looks up "foo_init()", declares "libfoo_init" pointer
DECLARE_SYM(free);
DECLARE_SYM(get);
DECLARE_SYM(set);

My favourite use is for dispatching CUDA function calls and checking their return value:

#define CUDACALL(F, ARGS...) do { e = F(ARGS); if (e != cudaSuccess) throw cudaException(#F, e); } while (false)

CUDACALL(cudaMemcpy, data, dp, s, cudaMemcpyDeviceToHost);
CUDACALL(cudaFree, dp);


回答3:

Since this an Open ended Question an trick which I often use and find convenient.

If you want to write an wrapper function over an free function like say malloc, without modifying each and every instance in your code where the function is called then a simple macro shall suffice:

#define malloc(X) my_malloc( X, __FILE__, __LINE__, __FUNCTION__)

void* my_malloc(size_t size, const char *file, int line, const char *func)
{

    void *p = malloc(size);
    printf ("Allocated = %s, %i, %s, %p[%li]\n", file, line, func, p, size);

    /*Link List functionality goes in here*/

    return p;
}

You can often use this trick to write your own memory leak detector etc, for debugging purposes.

Though the example is for malloc it can be re-used for any free standing function really.



回答4:

One example is token pasting if you want to use a value as both an identifier and a value. From the msdn link:

#define paster( n ) printf_s( "token" #n " = %d", token##n )
int token9 = 9;

paster( 9 ); // => printf_s( "token9 = %d", token9 );

There are also cases in the c++ faq where though there may be alternatives the macro solution is the best way to do things. One example is pointers to member functions where the right macro

 #define CALL_MEMBER_FN(object,ptrToMember)  ((object).*(ptrToMember)) 

makes it much easier to make the call rather than deal with all the assorted hair of trying to do it w/out the macro.

int ans = CALL_MEMBER_FN(fred,p)('x', 3.14);

Honestly I just take their word for it and do it this way, but apparently it gets worse as the calls become more complicated.

Here's an example of someone trying to go it alone



回答5:

When you need the call itself to optionally return from a function.

#define MYMACRO(x) if(x) { return; }
void fn()
{
    MYMACRO(a);
    MYMACRO(b);
    MYMACRO(c);
}

This is usually used for small bits of repetitive code.



回答6:

I am not sure that debugging macros take a lot of time. I would believe that I find simple the debugging of macros (even 100 lines monster macros), because you have the possibility to look at the expansion (using gcc -C -E for instance) - which is less possible with e.g. C++ templates.

C macros are useful when in several occasions:

  • you want to process a list of things in several different ways
  • you want to define an "lvalue" expression
  • you need efficiency
  • you need to have the location of the macro thru __LINE__)
  • you need unique identifiers
  • etc etc

Look at the many uses of #define-d macros inside major free softwaare (like Gtk, Gcc, Qt, ...)

What I regret a lot is that C macro language is so limited.... Imagine if the C macro language would have been as powerful as Guile!!! (Then you could write things as complex as flex or bison as macros).

Look at the power of Common Lisp macros!



回答7:

If you are using C, you need to use macros to simulate templates.

From http://www.flipcode.com/archives/Faking_Templates_In_C.shtml

#define CREATE_VECTOR_TYPE_H(type) \
typedef struct _##type##_Vector{ \
  type *pArray; \
  type illegal; \
  int size; \
  int len; \
} type##_Vector; \
void type##_InitVector(type##_Vector *pV, type illegal); \
void type##_InitVectorEx(type##_Vector *pV, int size, type illegal); \
void type##_ClearVector(type##_Vector *pV); \
void type##_DeleteAll(type##_Vector *pV); \
void type##_EraseVector(type##_Vector *pV); \
int type##_AddElem(type##_Vector *pV, type Data); \
type type##_SetElemAt(type##_Vector *pV, int pos, type data); \
type type##_GetElemAt(type##_Vector *pV, int pos);

#define CREATE_VECTOR_TYPE_C(type) \
void type##_InitVector(type##_Vector *pV, type illegal) \
{ \
  type##_InitVectorEx(pV, DEF_SIZE, illegal); \
} \
void type##_InitVectorEx(type##_Vector *pV, int size, type illegal) \
{ \
  pV-len = 0; \
  pV-illegal = illegal; \
  pV-pArray = malloc(sizeof(type) * size); \
  pV-size = size; \
} \
void type##_ClearVector(type##_Vector *pV) \
{ \
  memset(pV-pArray, 0, sizeof(type) * pV-size); \
  pV-len = 0; \
} \
void type##_EraseVector(type##_Vector *pV) \
{ \
  if(pV-pArray != NULL) \
    free(pV-pArray); \
  pV-len = 0; \
  pV-size = 0; \
  pV-pArray = NULL; \
} \
int type##_AddElem(type##_Vector *pV, type Data) \
{ \
  type *pTmp; \
  if(pV-len = pV-size) \
  { \
    pTmp = malloc(sizeof(type) * pV-size * 2); \
    if(pTmp == NULL) \
      return -1; \
    memcpy(pTmp, pV-pArray, sizeof(type) * pV-size); \
    free(pV-pArray); \
    pV-pArray = pTmp; \
    pV-size *= 2; \
  } \
  pV-pArray[pV-len] = Data; \
  return pV-len++; \
} \
type type##_SetElemAt(type##_Vector *pV, int pos, type data) \
{ \
  type old = pV-illegal; \
  if(pos = 0 && pos <= pV-len) \
  { \
    old = pV-pArray[pos]; \
    pV-pArray[pos] = data; \
  } \
  return old; \
} \
type type##_GetElemAt(type##_Vector *pV, int pos) \
{ \
  if(pos = 0 && pos <= pV-len) \
    return pV-pArray[pos]; \
  return pV-illegal; \
} 


回答8:

Consider the standard assert macro.

  • It uses conditional compilation to ensure that the code is included only in debug builds (rather than relying on the optimizer to elide it).
  • It uses the __FILE__ and __LINE__ macros to create references to the location in the source code.


回答9:

I've once used macro to generate a large string array along with index enumeration:

strings.inc

GEN_ARRAY(a)
GEN_ARRAY(aa)
GEN_ARRAY(abc)
GEN_ARRAY(abcd)
// ...

strings.h

// the actual strings
#define GEN_ARRAY(x) #x ,
const char *strings[]={
    #include "strings.inc"
    ""
};
#undef GEN_ARRAY

// indexes
#define GEN_ARRAY(x) enm_##x ,
enum ENM_string_Index{
    #include "strings.inc"
    enm_TOTAL
};
#undef GEN_ARRAY

It is useful when you have several array that has to be kept synchronized.



回答10:

To expand on @tenfour's answer about conditional returns: I do this a lot when writing Win32/COM code where it seems I'm checking an HRESULT every second line. For example, compare the annoying way:

// Annoying way:
HRESULT foo() {
    HRESULT hr = SomeCOMCall();
    if (SUCCEEDED(hr)) {
        hr = SomeOtherCOMCall();
    }

    if (SUCCEEDED(hr)) {
        hr = SomeOtherCOMCall2();
    }

    // ... ad nauseam.

    return hr;
}

With the macro-y nice way:

// Nice way:
HRESULT foo() {
    SUCCEED_OR_RETURN(SomeCOMCall());
    SUCCEED_OR_RETURN(SomeOtherCOMCall());
    SUCCEED_OR_RETURN(SomeOtherCOMCall2());

    // ... ad nauseam.

    // If control makes it here, nothing failed.
    return S_OK;
}

It's doubly handy if you wire up the macro to log any failures automatically: using other macro ideas like token pasting and FILE, LINE, etc; I can even make the log entry contain the code location and the expression that failed. You could also throw an assert in there if you wanted to!

#define SUCCEED_OR_RETURN(expression) { \
    HRESULT hrTest = (expression); \
    if (!SUCCEEDED(hrTest)) { \
        logFailure( \
            #expression, \
            HResultValueToString(hrTest), \
            __FILE__, \
            __LINE__, \
            __FUNCTION__); \
        return hrTest; \
    } \
}


回答11:

Debugging would be much easier as your project will be in divided into various modules for each task. Macros can be very useful when you have a large and complex software project. But there are some pitfalls which are stated here.



回答12:

For me it's more comfortable to use macros for constants and for parts of code that have no separated logical functionality. But there are some important differences between (inline) functions and (function-like) macros, here they are: http://msdn.microsoft.com/en-us/library/bf6bf4cf.aspx



标签: c++ c macros