#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 = now

   You 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
= now

   This 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: 

复制代码
- (void)calledALot
{
if(...) // only time some calls
NSTimeInterval now = [[NSProcessInfo processInfo] systemUptime];
if(_calledALotLastTimeIvar)
NSLog(
@"%s: %f seconds", name, now - _calledALotLastTimeIvar);
_calledALotLastTimeIvar
= now;
}
复制代码
   This won't compile. Declaring NSTimeInterval now in the if statement is illegal. Even if that worked, only the first statement is subject to the if, 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.

复制代码
#define TIME(name, lastTimeVariable) \
do { \
NSTimeInterval now
= [[NSProcessInfo processInfo] systemUptime]; \
if(lastTimeVariable) \
NSLog(
@"%s: %f seconds", name, now - lastTimeVariable); \
lastTimeVariable
= now; \
}
while(0)
复制代码
   This works correctly with the if statement and in all other situations. A multi-statement macro should always be wrapped in do ... 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 == 2
复制代码

However, 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 happened

   This 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
constchar*MyEnumToString(enum MyEnum value)
{
#define MY_ENUM_MEMBER(x) if(value == (x)) return #x;
MY_ENUM
#undef MY_ENUM_MEMBER
}

// destringification
enum MyEnum MyEnumFromString(constchar*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

posted on 2012-12-14 11:28  operation_master  阅读(349)  评论(0编辑  收藏  举报

导航