#define In C [转摘]
Multi-Statement Macros
It's common to write a macro that consists of multiple statements. For example, a timing macro:
#define TIME(name, lastTimeVariable) NSTimeInterval now = [[NSProcessInfo processInfo] systemUptime]; if(lastTimeVariable) NSLog(@"%s: %f seconds", name, now - lastTimeVariable); lastTimeVariable = nowYou would use this macro in some frequently called method to measure just how frequently it's called:
- (void)calledALot
{
// do some work
// time it
TIME("calledALot", _calledALotLastTimeIvar);
}This definition works well enough here, but it's unbelievably ugly sitting all on one line like this. Let's split it up onto multiple lines. Normally a
#define
is terminated at the end of the line, but by putting\
at the end of the line, you can make the preprocessor continue the definition on the next line:#define TIME(name, lastTimeVariable) \
NSTimeInterval now = [[NSProcessInfo processInfo] systemUptime]; \
if(lastTimeVariable) \
NSLog(@"%s: %f seconds", name, now - lastTimeVariable); \
lastTimeVariable = nowThis is easier to work with. However, the macro is flawed. Consider this use:
- (void)calledALot
{
if(...) // only time some calls
TIME("calledALot", _calledALotLastTimeIvar);
}The macro will expand like this:
This won't compile. Declaring- (void)calledALot
{
if(...) // only time some calls
NSTimeInterval now = [[NSProcessInfo processInfo] systemUptime];
if(_calledALotLastTimeIvar)
NSLog(@"%s: %f seconds", name, now - _calledALotLastTimeIvar);
_calledALotLastTimeIvar = now;
}NSTimeInterval now
in theif
statement is illegal. Even if that worked, only the first statement is subject to theif
, and the following lines would run regardless. Not what we wanted!
This can be solved by putting brackets around the macro definition:
#define TIME(name, lastTimeVariable) \
{ \
NSTimeInterval now = [[NSProcessInfo processInfo] systemUptime]; \
if(_calledALotLastTimeIvar) \
NSLog(@"%s: %f seconds", name, now - _calledALotLastTimeIvar); \
_calledALotLastTimeIvar = now; \
}Now the expansion looks like this:
- (void)calledALot
{
if(...) // only time some calls
{
NSTimeInterval now = [[NSProcessInfo processInfo] systemUptime];
if(lastTimeVariable)
NSLog(@"%s: %f seconds", name, now - lastTimeVariable);
lastTimeVariable = now;
};
}Pretty good, except for that surplus semicolon at the end. Not a problem, though... right?
In fact, this is a problem. Consider this code:
- (void)calledALot
{
if(...) // only time some calls
TIME("calledALot", _calledALotLastTimeIvar);
else // otherwise do something else
// stuff
}The expansion then looks like this:
- (void)calledALot
{
if(...) // only time some calls
{
NSTimeInterval now = [[NSProcessInfo processInfo] systemUptime];
if(_calledALotLastTimeIvar)
NSLog(@"%s: %f seconds", name, now - _calledALotLastTimeIvar);
_calledALotLastTimeIvar = now;
};
else // otherwise do something else
// stuff
}That semicolon now causes a syntax error. Whoops.
You could work around this by requiring the user of the macro not to put a semicolon at the end. However, this is highly unnatural and tends to mess with things like automatic code indenting.
A better way to fix it is to wrap the function in a
do ... while(0)
construct. This construct requires a semicolon at the end, which is exactly what we want. Usingwhile(0)
ensures that the loop never really loops, and its contents are only executed once.This works correctly with the#define TIME(name, lastTimeVariable) \
do { \
NSTimeInterval now = [[NSProcessInfo processInfo] systemUptime]; \
if(lastTimeVariable) \
NSLog(@"%s: %f seconds", name, now - lastTimeVariable); \
lastTimeVariable = now; \
} while(0)if
statement and in all other situations. A multi-statement macro should always be wrapped indo ... while(0)
for this reason.
Stringification
By placing a # in front of a parameter name, the preprocessor will turn the contents of that parameter into a C string. For example:#define TEST(condition) \
do { \
if(!(condition)) \
NSLog(@"Failed test: %s", #condition); \
} while(0)
TEST(1 == 2);
// logs: Failed test: 1 == 2However, you have to be careful with this. If the parameter contains a macro, it will not be expanded. For example:
#define WITHIN(x, y, delta) (fabs((x) - (y)) < delta)
TEST(WITHIN(1.1, 1.2, 0.05));
// logs: Failed test: WITHIN(1.1, 1.2, 0.05)Sometimes this is desirable, but sometimes it's not. To avoid it, you can add an extra level of indirection:
#define STRINGIFY(x) #x
#define TEST(condition) \
do { \
if(!(condition)) \
NSLog(@"Failed test: %s", STRINGIFY(condition)); \
} while(0)
TEST(WITHIN(1.1, 1.2, 0.05));
// logs: Failed test: (fabs(1.1 - 1.2) < 0.05)For this particular case, the desired behavior is pretty much a matter of opinion. In other cases, it may be obvious that you only want one or the other.
Magic Identifiers
C provides several built-in identifiers which can be extremely useful when building macros:
- __LINE__: a built-in macro that expands to the current line number.
- __FILE__: another built-in macro that expands to a string literal containing the name of the current source file.
- __func__: this is an implicit variable which contains the name of the current function as a C string.
Note that when used in a macro definition, these will all refer to the place where your macro is used, not where it is defined, which is really useful.
As an example, consider this logging macro:
#define LOG(fmt, ...) NSLog(@"%s:%d (%s): " fmt, __FILE__, __LINE__, __func__, ## __VA_ARGS__)This is not too exciting. Let's use these built-in identifiers to make it more interesting:
#define LOG(fmt, ...) NSLog(fmt, ## __VA_ARGS__)This is much more interesting. You can write a log like this:
LOG("something happened");The output will look like this:
MyFile.m:42 (MyFunction): something happenedThis is an extremely valuable debugging aid. You can sprinkle LOG statements throughout your code and the log output will automatically contain the file name, line number, and function name of where each log statement was placed.
X-Macros
This is something I've never used, but is interesting enough that it deserves mention. X-macros are a way of defining a macro in terms of another macro, which are then redefined multiple times to give that macro new meaning. This is confusing, so here's an example:
#define MY_ENUM \
MY_ENUM_MEMBER(kStop) \
MY_ENUM_MEMBER(kGo) \
MY_ENUM_MEMBER(kYield)
// create the actual enum
enum MyEnum {
#define MY_ENUM_MEMBER(x) x,
MY_ENUM
#undef MY_ENUM_MEMBER
};
// stringification
const char *MyEnumToString(enum MyEnum value)
{
#define MY_ENUM_MEMBER(x) if(value == (x)) return #x;
MY_ENUM
#undef MY_ENUM_MEMBER
}
// destringification
enum MyEnum MyEnumFromString(const char *str)
{
#define MY_ENUM_MEMBER(x) if(strcmp(str, #x) == 0) return x;
MY_ENUM
#undef MY_ENUM_MEMBER
// default value
return -1;
}This is an advanced and frightening technique, but it could help eliminate a lot of boring repetition in certain specialized cases. For more information about X-macros, consult the Wikipedia article.
Conclusion
C macros are complicated and powerful. If you use them, you must be extremely careful not to abuse them. However, in some situations they can be incredibly useful, and, when used correctly, these tips and tricks can help you create macros which make your code easier to write and easier to read.
Original Page : http://www.mikeash.com/pyblog/friday-qa-2010-12-31-c-macro-tips-and-tricks.html