Jump to content

  • Log In with Google      Sign In   
  • Create Account


#ActualEctara

Posted 22 March 2013 - 03:15 AM

What does that have to do with macros and functions?



Shogun.

 

Everything. If it were a function, it'd be conceptually like this:

x = *(ptr++);
if(x){
        delete x;
        x = NULL;
}

Since it is a macro, which deals only with tokens and knows nothing about the other phases of compilation, it looks (literally) like this:

if(*(ptr++)) { delete *(ptr++); *(ptr++) = NULL; }

Notice how I only increment once in the invocation of the macro, but the token "x" is replaced with the macro parameter three times, resulting in it incrementing the pointer three separate times. This puts it past the end of the array, and causes undefined behavior.

Anybody (you, or someone else that uses your code) might make the assumption that this wouldn't happen. You might forget the body of the macro, and later on do something like I posted, which will crash the program. My policy on it is if the macro is to be used outside of that module that it was defined, it should not have any multiple-evaluation side effects; otherwise, I should provide it as a function, instead. I only use "unsafe" (in this sense) macros where I know what all of the inputs will be, and can guarantee that it will be safe.

I'm sure it's in a C/C++ FAQ somewhere, but here are a couple articles I found with a quick search:
http://www.brainbell.com/tutors/c/Advice_and_Warnings_for_C/Macros_and_Miscellaneous_Pitfalls.html
http://www.mikeash.com/pyblog/friday-qa-2010-12-31-c-macro-tips-and-tricks.html

The reason we're asking is because there are now better tools to do these things, and if you are not careful, this can come back to bite you. Also, most debuggers can't step into a macro invocation; they'll display the line where the macro was invoked, so it is up to you to figure out how the preprocessor expanded the macro, and what went wrong by hand, or dump the preprocessed output through compiler switches, and sift through it manually.

 

compiler doesn't have to push the parameters, call the function, pop them off the stack, do push the registers (eax, ebx, etc.), do whatever, restore the registers and return.


Also, I want to point out, that this is exactly what inline functions were designed to do. The body of the function is compiled "inline" where it is called. For your example, a macro and an inline function would have more or less the exact same result. Different compilers have different rules for what they will and won't inline, and how small the function must be for it to be inlined. However, for an inline function to do this task, it will do none of the things you mentioned, and essentially generate the same code that the macro would have, given that it was used correctly. It might even wind up being more efficient, by storing the result of x once and reusing it, rather than recalculating it three times, with potentially harmful side-effects.

#1Ectara

Posted 22 March 2013 - 02:10 AM

What does that have to do with macros and functions?



Shogun.

 

Everything. If it were a function, it'd be conceptually like this:

x = *(ptr++);
if(x){
        delete x;
        x = NULL;
}

Since it is a macro, which deals only with tokens and knows nothing about the other phases of compilation, it looks (literally) like this:

if(*(ptr++)) { delete *(ptr++); *(ptr++) = NULL; }

Notice how I only increment once in the invocation of the macro, but the token "x" is replaced with the macro parameter three times, resulting in it incrementing the pointer three separate times. This puts it past the end of the array, and causes undefined behavior.

Anybody (you, or someone else that uses your code) might make the assumption that this wouldn't happen. You might forget the body of the macro, and later on do something like I posted, which will crash the program. My policy on it is if the macro is to be used outside of that module that it was defined, it should not have any multiple-evaluation side effects; otherwise, I should provide it as a function, instead. I only use "unsafe" (in this sense) macros where I know what all of the inputs will be, and can guarantee that it will be safe.

I'm sure it's in a C/C++ FAQ somewhere, but here are a couple articles I found with a quick search:
http://www.brainbell.com/tutors/c/Advice_and_Warnings_for_C/Macros_and_Miscellaneous_Pitfalls.html
http://www.mikeash.com/pyblog/friday-qa-2010-12-31-c-macro-tips-and-tricks.html

The reason we're asking is because there are now better tools to do these things, and if you are not careful, this can come back to bite you. Also, most debuggers can't step into a macro invocation; they'll display the line where the macro was invoked, so it is up to you to figure out how the preprocessor expanded the macro, and what went wrong by hand, or dump the preprocessed output through compiler switches, and sift through it manually.


PARTNERS