I have a function-like macro that takes in an enum return code and a function call.
#define HANDLE_CALL(result,call) \
do { \
Result callResult = call; \
Result* resultVar = (Result*)result; \
// some additional processing
(*resultVar) = callResult; \
} while(0)
Does fancy pointer casting to Result* and subsequent de-referencing gain you anything? That is, is there any advantage to doing this over just:
callResult = call;
// additional processing
*result = callResult;
The macro is used like this:
Result result;
HANDLE_CALL(&result,some_function());
By the way, this isn’t my code and I’m not a power C user so I’m just trying to understand if there is any logic behind doing this.
I think what it gives you, is that the user of the macro can pass in a pointer of any old type, not just
Result*. Personally I’d either do it your way, or if I really wanted to allow (for example) avoid*macro argument I’d write*(Result*)(result) = callResult;.There’s another thing it might be, depending what the rest of the macro looks like. Does “some additional processing” mention
resultVarat all, or is the line(*resultVar) = callResult;conditional? If so, thenresultVarexists in order to ensure that the macro evaluates each of its arguments exactly once, and therefore behaves more like a function call than it would if it evaluated them any other number of times (including zero).So, if you call it in a loop like
HANDLE_CALL(output++, *input++)then it does something vaguely predictable. At least, it does provided thatoutputis aResult*as the macro author intended. I’m still not sure what the cast gives you, other than allowing different argument types likevoid*orchar*.There are some situations where it could make another difference whether you have that extra cast or not. For example, consider:
if
typedef double Result;isn’t visible on screen, the other three lines appear pretty innocuous. What’s wrong with assigning an int to an int, right? But once the macro is expanded, bad stuff happens when you cast anint*todouble*. With the cast, that bad stuff is undefined behavior, most likelydoubleis bigger thanintand it overruns the memory fori. If you’re lucky, you’ll get a compiler warning about “strict aliasing”.Without the cast you can’t do
double* resultVar = &i;, so the original macro with that change catches the error and rejects the code, instead of doing something nasty. Your version with*result = callResult;actually works, provided thatdoublecan accurately represent every value ofint. Which with an IEEE double and anintsmaller than 53 bits, it can.Presumably
Resultis really a struct, and nobody would really write the code I give above. But I think it serves as an example why macros always end up being more fiddly than you think.