Macros in C/C++ are basically little rules that are defined by a preprocessor and substituted into the code that the compiler ultimately attempts to compile.
Modern coding practice these days is to use inline functions and constants instead of macros.
But the reality is they can still be (ab)used and code often does. For example code might insert debug statements or logging which is compiled away in release mode.
Another common use is on Windows where the type
TCHAR compiles to be either
wchar_t depending on
#define UNICODE being present or not. Along with it go macros like
T2CW etc. Code should compile cleanly either way but the reality is usually it doesn't.
A classic problem would be something like this:
// And in code float result = SQUARED(++x); That would expand to float result = ++x * ++x;
So the value in result would be wrong and the value in x would be incremented twice.
Consider we are compiling this structure:
// Header struct Tooltip char buffer; char buffer; };
And in C++
Tooltip tooltip; memset(&tooltip, 0, sizeof(tooltip));
If we fail to define
TOOLTIP_VERSION to the same value in the implementation as in the caller, then this code may stomp all over memory because it thinks the struct is 128 bytes in one place and 64 bytes in another.
Macros aren't namespaced and in some cases this leads to problems where a macro definition collides with a well qualified symbol.
For example code that
#include <windows.h> gets a
#define TRUE 1. But that excludes any other code that expects to compile on Windows from ever using
TRUE as a const no matter how well they qualify it. Consequently code has to do workarounds such as
#undef macros to make code work or using another value.
bool value = myapp::TRUE;
Ugh. But more likely we'll rename myapp::TRUE to something like myapp::MYAPP_TRUE to avoid the conflict. It's still an ugly workaround for a problem caused by inconsiderate use of macros.
Commonly used words like TRUE, FALSE, ERROR, OK, SUCCESS, FAIL are more or less unusable thanks to macros.
How Rust helps
Rust provides developers with consts, inline attributes, and platform / architecture attributes for the purpose of conditional compilation.
Rust offers macros but they consist of a set of matching rules than must generate syntactically Rust. Macro expansion is performed by the compiler so it is capable of generating errors on the macro if the macro is in error.