D C*_*zee 107
宏很容易出错,因为它们依赖于文本替换而不执行类型检查.例如,这个宏:
#define square(a) a * a
Run Code Online (Sandbox Code Playgroud)
与整数一起使用时工作正常:
square(5) --> 5 * 5 --> 25
Run Code Online (Sandbox Code Playgroud)
但与表达式一起使用时会有很奇怪的事情:
square(1 + 2) --> 1 + 2 * 1 + 2 --> 1 + 2 + 2 --> 5
square(x++) --> x++ * x++ --> increments x twice
Run Code Online (Sandbox Code Playgroud)
在括号周围加上括号有助于但不能完全消除这些问题.
当宏包含多个语句时,您可能会遇到控制流构造的问题:
#define swap(x, y) t = x; x = y; y = t;
if (x < y) swap(x, y); -->
if (x < y) t = x; x = y; y = t; --> if (x < y) { t = x; } x = y; y = t;
Run Code Online (Sandbox Code Playgroud)
解决这个问题的通常策略是将语句放在"do {...} while(0)"循环中.
如果你有两个结构碰巧包含一个具有相同名称但语义不同的字段,那么同一个宏可能同时适用于两者,结果很奇怪:
struct shirt
{
int numButtons;
};
struct webpage
{
int numButtons;
};
#define num_button_holes(shirt) ((shirt).numButtons * 4)
struct webpage page;
page.numButtons = 2;
num_button_holes(page) -> 8
Run Code Online (Sandbox Code Playgroud)
最后,宏可能难以调试,产生奇怪的语法错误或运行时错误,您必须扩展才能理解(例如使用gcc -E),因为调试器无法单步执行宏,如下例所示:
#define print(x, y) printf(x y) /* accidentally forgot comma */
print("foo %s", "bar") /* prints "foo %sbar" */
Run Code Online (Sandbox Code Playgroud)
内联函数和常量有助于避免宏中的许多这些问题,但并不总是适用.在故意使用宏来指定多态行为的情况下,可能难以避免无意的多态性.C++具有许多功能,例如模板,可以在不使用宏的情况下以类型安全的方式创建复杂的多态结构; 有关详细信息,请参阅Stroustrup的C++编程语言.
zan*_*ngw 35
宏功能:
功能特点:
Mys*_*ial 30
副作用很大.这是一个典型案例:
#define min(a, b) (a < b ? a : b)
min(x++, y)
Run Code Online (Sandbox Code Playgroud)
扩大到:
(x++ < y ? x++ : y)
Run Code Online (Sandbox Code Playgroud)
x在同一语句中增加两次.(和未定义的行为)
编写多行宏也很痛苦:
#define foo(a,b,c) \
a += 10; \
b += 10; \
c += 10;
Run Code Online (Sandbox Code Playgroud)
它们需要\在每一行的末尾.
除非你把它作为单个表达式,否则宏不能"返回"任何东西:
int foo(int *a, int *b){
side_effect0();
side_effect1();
return a[0] + b[0];
}
Run Code Online (Sandbox Code Playgroud)
除非您使用GCC的表达式语句,否则无法在宏中执行此操作.(编辑:你可以使用逗号运算符......忽略了......但它可能仍然不太可读.)
运营顺序:(由@ouah提供)
#define min(a,b) (a < b ? a : b)
min(x & 0xFF, 42)
Run Code Online (Sandbox Code Playgroud)
扩大到:
(x & 0xFF < 42 ? x & 0xFF : 42)
Run Code Online (Sandbox Code Playgroud)
但&优先级低于<.所以先0xFF < 42得到评估.
Fle*_*exo 13
#define SQUARE(x) ((x)*(x))
int main() {
int x = 2;
int y = SQUARE(x++); // Undefined behavior even though it doesn't look
// like it here
return 0;
}
Run Code Online (Sandbox Code Playgroud)
然而:
int square(int x) {
return x * x;
}
int main() {
int x = 2;
int y = square(x++); // fine
return 0;
}
Run Code Online (Sandbox Code Playgroud)
struct foo {
int bar;
};
#define GET_BAR(f) ((f)->bar)
int main() {
struct foo f;
int a = GET_BAR(&f); // fine
int b = GET_BAR(&a); // error, but the message won't make much sense unless you
// know what the macro does
return 0;
}
Run Code Online (Sandbox Code Playgroud)
相比:
struct foo {
int bar;
};
int get_bar(struct foo *f) {
return f->bar;
}
int main() {
struct foo f;
int a = get_bar(&f); // fine
int b = get_bar(&a); // error, but compiler complains about passing int* where
// struct foo* should be given
return 0;
}
Run Code Online (Sandbox Code Playgroud)
ide*_*n42 12
如有疑问,请使用函数(或内联函数).
然而,这里的答案主要解释宏的问题,而不是有一些简单的观点,宏是邪恶的,因为愚蠢的事故是可能的.
你可以意识到陷阱并学会避免它们.然后只有在有充分理由的情况下才使用宏.
在某些特殊情况下,使用宏有一些优势,包括:
va_args.__FILE__,__LINE__,__func__).检查前/后条件,assert失败,甚至静态断言,因此代码不会在不正确的使用时编译(主要用于调试版本).struct在转换之前检查成员是否存在func(FOO, "FOO");,你可以定义一个宏来为你扩展字符串func_wrapper(FOO);inline函数可能是一个选项).不可否认,其中一些依赖于非标准C的编译器扩展.这意味着您最终可能会使用较少的可移植代码,或者必须使用ifdef它们,因此只有在编译器支持时才会利用它们.
注意到这是因为它是宏中最常见的错误原因之一(x++例如,传入宏可能会增加多次).
它可以编写宏来避免副参数的多重实例化.
如果你想拥有square适用于各种类型并且支持C11的宏,你可以这样做......
inline float _square_fl(float a) { return a * a; }
inline double _square_dbl(float a) { return a * a; }
inline int _square_i(int a) { return a * a; }
inline unsigned int _square_ui(unsigned int a) { return a * a; }
inline short _square_s(short a) { return a * a; }
inline unsigned short _square_us(unsigned short a) { return a * a; }
/* ... long, char ... etc */
#define square(a) \
_Generic((a), \
float: _square_fl(a), \
double: _square_dbl(a), \
int: _square_i(a), \
unsigned int: _square_ui(a), \
short: _square_s(a), \
unsigned short: _square_us(a))
Run Code Online (Sandbox Code Playgroud)
这是GCC,Clang,EKOPath和Intel C++支持的编译器扩展(但不支持MSVC) ;
#define square(a_) __extension__ ({ \
typeof(a_) a = (a_); \
(a * a); })
Run Code Online (Sandbox Code Playgroud)
因此,宏的缺点是你需要知道使用它们开始,并且它们不被广泛支持.
一个好处是,在这种情况下,您可以square为许多不同类型使用相同的功能.
添加到这个答案..
宏由预处理器直接替换为程序(因为它们基本上是预处理器指令).因此,它们不可避免地使用比相应功能更多的存储空间.另一方面,函数需要更多时间来调用并返回结果,并且可以通过使用宏来避免这种开销.
此外,宏还有一些特殊的工具,可以帮助不同平台上的程序可移植性.
与函数相比,不需要为其参数分配宏数据类型.
总的来说,它们是编程中的有用工具.并且可以根据情况使用宏指令和函数.