26

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?

Terry Li
  • 16,870
  • 30
  • 89
  • 134
  • 2
    I abuse them when writing HPC code - as they are far more flexible than anything else. But that's just a small niche... – Mysticial Dec 14 '11 at 18:34
  • 1
    Where is the quote from? – Taylor Price Dec 14 '11 at 18:38
  • 3
    @TaylorPrice: it looks like a comment I wrote [here](http://stackoverflow.com/a/8509285/204847). – Mike Seymour Dec 14 '11 at 18:39
  • @TaylorPrice From my previous question, someone pointed that out for me. In case you were curious, here's the link to the question:http://stackoverflow.com/questions/8508989/whats-going-on-with-c-macro-multiplication – Terry Li Dec 14 '11 at 18:40
  • Thanks guys. Not disagreeing with the quote at all. I was just curious if it was from some popular software engineering book I hadn't read yet or from somewhere else. – Taylor Price Dec 14 '11 at 18:47
  • 1
    The case where someone is pointing a gun at your head and saying "if you don't use a macro I'll shoot you". – DJClayworth Dec 14 '11 at 21:50

12 Answers12

34

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

Community
  • 1
  • 1
Xeo
  • 129,499
  • 52
  • 291
  • 397
  • @Mysticial: Since that was my main point, I added a little more emphasis on it. :) – Xeo Dec 15 '11 at 02:30
  • @Xeo I don't suppose there is anything that static_assert can't do that the macro assert can? – Trevor Hickey Dec 17 '13 at 15:13
  • @Trevor: `static_assert` cannot be conditionally defined to do something or nothing - and it especially can't do anything at runtime. – Xeo Dec 17 '13 at 16:33
11

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);
Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
8

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.

Alok Save
  • 202,538
  • 53
  • 430
  • 533
7

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

Paul Rubel
  • 26,632
  • 7
  • 60
  • 80
  • 3
    But there are not casts in you member function invocation. And the `(object.*ptrToMember)` syntax is short and clear... – André Caron Dec 14 '11 at 18:45
  • Your macro is really not a good example, especially with C++11's variadic templates, which allow you to build that functionality as a function template. See [here](http://ideone.com/f3w6c) for an example. – Xeo Dec 14 '11 at 19:02
  • good point, it can be a lot of parens, but they aren't casts. Fixed. – Paul Rubel Dec 14 '11 at 19:04
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.

Alok Save
  • 202,538
  • 53
  • 430
  • 533
tenfour
  • 36,141
  • 15
  • 83
  • 142
4

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!

Basile Starynkevitch
  • 223,805
  • 18
  • 296
  • 547
  • Looking at the preprocessed source is not nearly as useful as being able to step through with a debugger. In the case of templates, there is no "expansion", the code doesn't change. – tenfour Dec 22 '11 at 14:18
4

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; \
} 
Marlon
  • 19,924
  • 12
  • 70
  • 101
4

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.
Basile Starynkevitch
  • 223,805
  • 18
  • 296
  • 547
Adrian McCarthy
  • 45,555
  • 16
  • 123
  • 175
4

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.

fefe
  • 3,342
  • 2
  • 23
  • 45
2

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; \
    } \
}
Seth Davenport
  • 389
  • 1
  • 5
1

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.

Muhammad Maqsoodur Rehman
  • 33,681
  • 34
  • 84
  • 124
0

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

n0p
  • 713
  • 10
  • 23