16 Oct 10

Macro blues and function reentry?

Knowing how to macro correctly is important in both the real world (starcraft) and when writing code.

in c you can define useful macros like

#define FFMAX(a,b) ((a) > (b) ? (a) : (b))

. The macros will be a little better than using even static inline functions because no extra memory is allocated for the temporary variables that are implicit in a function like

static inline int max(int a, int b) { return a > b ? a : b; }

But using macros causes, as I found, REEAALLY if-not-hard-stupid-to-find bugs if you are putting expressions with side effects as the macro arguments. If you don’t know, stupid-to-find means you feel increasingly stupid during and after the finding.

I had a function with side effects that was in the macro, and I when I got some strange results I turned to valgrind and gdb to help find the problem. I also added a bunch of printfs. All this told me was that the function was being executed twice on the same line even though I had written it just once, and it wasn’t near a loop. I had gotten so used to ignoring the debugger when it jumps to the MAX(a, b) macro that I didn’t even think to look at the definition, which was of course the root of the problem. The problem is that macros do text substitution on its argument list, and not evaluation as normal functions do. This means that when I called

FFMAX(smallintval, my_side_effecting_function())

it expanded naturally to

((smallintval) > (my_side_effecting_function()) ? (smallintval) : (my_side_effecting_function()))

and caused double side effects to happen instead of the single.
This guy here has explained it much better than I.

The real problem though, is that from watching the debugger jump into the same function twice from the same line, I expected that there was some crazy stack thrashing causing reentry of some sort or some The Matrix-style “deja vu” (what the hell was that about anyway?) So I ran valgrind memcheck on it to of course no avail, because there was no buffer overflows, no memory bashing, no elegant landing on the program counter memcpy mistakes.

I stared and played around with the code for about 8 hours straight; I got it, of course, by then going to sleep and looking at it with a fresh eyes.
Here is a memento screenshot: