C中的macros与函数
我在C编程
我总是看到使用macros比使用函数更好的例子和案例。
有人可以解释一个例子与一个函数比较macros的缺点吗?
macros很容易出错,因为它们依赖于文本replace,并且不执行types检查。 例如,这个macros:
#define square(a) a*a
与整数一起使用时工作正常:
square(5) --> 5*5 --> 25
但在与expression式一起使用时会做很奇怪的事情:
square(1+2) --> 1+2*1+2 --> 1+2+2 --> 5 square(x++) --> x++*x++ --> increments x twice
把括号括起来有助于但不能完全消除这些问题。
当macros包含多个语句时,可能会遇到控制stream结构的麻烦:
#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;
解决这个问题的通常策略是将语句放在“do {…} while(0)”循环中。
如果你有两个结构碰巧包含一个名称相同但语义不同的字段,那么同一个macros可能同时工作,结果很奇怪:
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
最后,macros可能很难debugging,产生奇怪的语法错误或运行时错误,您必须扩展才能理解(例如使用gcc -E),因为debugging器不能跨越macros,如下例所示:
#define print(x, y) printf(xy) /* accidentally forgot comma */ print("foo %s", "bar") /* prints "foo %sbar" */
内联函数和常量有助于避免macros中的许多这些问题,但并不总是适用的。 在有意使用macros指定多态行为的地方,无意的多态性可能难以避免。 C ++有许多特性,比如模板,可以在不使用macros的情况下以types安全的方式创build复杂的多态结构; 有关详细信息,请参阅Stroustrup的The C ++编程语言 。
副作用是一个很大的问题。 这是一个典型的案例:
#define min(a,b) (a < b ? a : b) min(x++,y)
扩展到:
(x++ < y ? x++ : y)
x
在相同的语句中增加两次。 (和未定义的行为)
编写多行macros也是一件痛苦的事情:
#define foo(a,b,c) \ a += 10; \ b += 10; \ c += 10;
他们需要在每一行的结尾。
macros不能“返回”任何东西,除非你把它做成一个expression式:
int foo(int *a, int *b){ side_effect0(); side_effect1(); return a[0] + b[0]; }
除非你使用GCC的expression式语句,否则不能在macros中做到这一点。 (编辑:您可以使用逗号运算符,虽然…忽略了…但它可能仍然不太可读。)
行动顺序:(礼貌@ouah)
#define min(a,b) (a < b ? a : b) min(x & 0xFF, 42)
扩展到:
(x & 0xFF < 42 ? x & 0xFF : 42)
但是&
优先级低于<
。 所以0xFF < 42
被首先评估。
重复参数和代码的types检查不会导致代码膨胀。 macros语法也可能导致任意数量的奇怪的边缘情况,其中可能会出现分号或优先顺序。 这里有一个链接,表明一些macros观的邪恶
例1:
#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; }
然而:
int square(int x) { return x * x; } int main() { int x = 2; int y = square(x++); // fine return 0; }
例2:
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; }
相比:
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; }
macrosfunction :
- macros是预处理的
- 没有types检查
- 代码长度增加
- macros的使用会导致副作用
- 执行速度更快
- 编译之前macros名称被macros值replace
- 小代码出现很多时候很有用
- macros不检查编译错误
function特点 :
- 函数被编译
- types检查已完成
- 代码长度保持不变
- 没有副作用
- 执行速度较慢
- 在函数调用期间,控制权转移发生
- 大代码出现很多时候很有用
- 函数检查编译错误
如有疑问,请使用函数(或内联函数)。
但是,这里的答案主要是解释macros的问题,而不是简单地认为macros是邪恶的,因为愚蠢的事故是可能的。
你可以意识到陷阱,并学会避免它们。 那么只有在有充分的理由时才使用macros。
有一些特殊情况下使用macros有好处,包括:
- 通用函数,如下所述,你可以有一个macros,可以用于不同types的input参数。
- 可变数量的参数可以映射到不同的函数,而不是使用C的
va_args
。
例如: https : //stackoverflow.com/a/24837037/432509 。 - 他们可以select包含本地信息,如debuggingstring:
(__func__
__FILE__
,__func__
__FILE__
,__func__
)。 检查前置/后置条件,assert
,甚至是静态断言,这样代码就不会在不当使用时编译(主要用于debugging版本)。 - 检查input参数,你可以对input参数进行testing,比如检查它们的types,sizeof,在铸造之前检查
struct
是否存在
(可以用于多态types) 。
或者检查一个数组是否符合一些长度条件
请参阅: https : //stackoverflow.com/a/29926435/432509 - 虽然它指出函数做types检查,但C也会强制使用值(例如int / float)。 在极less数情况下,这可能是有问题的。 有可能编写更严格的macros,然后是关于其input参数的函数。 请参阅: https : //stackoverflow.com/a/25988779/432509
- 他们用作包装函数,在某些情况下,你可能想避免重复自己,例如…
func(FOO, "FOO");
,你可以定义一个扩展string的macrosfunc_wrapper(FOO);
- 当你想在调用者本地作用域中操作variables时,传递指针的指针正常工作得很好,但是在某些情况下,使用macros的问题仍然很less。
(分配给多个variables,对于每个像素的操作,就是一个例子,你可能更喜欢一个macros,而不是一个函数,尽pipe它仍然依赖于上下文,因为inline
函数可能是一个选项) 。
无可否认,其中的一些依赖于不是标准C的编译器扩展。这意味着你可能会得到较less的可移植代码,或者不得不将它们ifdef
去,所以只有在编译器支持的时候才会利用它们。
避免多个参数实例化
注意到这一点,因为它是macros中最常见的错误原因之一(例如在x++
传递,macros可能多次增加) 。
它可能写入macros,避免副作用与多个实例的参数。
C11通用
如果你喜欢有与各种types的C11支持的square
macros,你可以这样做…
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))
语句expression式
这是GCC,Clang,EKOPath和Intel C ++ (但不是MSVC)支持的编译器扩展;
#define square(a_) __extension__ ({ \ typeof(a_) a = (a_); \ (a * a); })
所以macros的缺点是你需要知道使用这些开始,而不是广泛支持。
一个好处是,在这种情况下,您可以为许多不同的types使用相同的square
函数。
macros的一个缺点是debugging器读取的源代码没有扩展macros,因此在macros中运行debugging器并不一定有用。 不用说,你不能像使用函数一样在macros内设置断点。
函数做types检查。 这给你一个额外的安全层。
添加到这个答案..
macros由预处理器直接replace到程序中(因为它们基本上是预处理器指令)。 所以他们不可避免地使用比各自的function更多的内存空间。 另一方面,一个函数需要更多的时间来调用并返回结果,这个开销可以通过使用macros来避免。
macros也有一些特殊的工具,可以帮助程序在不同的平台上移植。
与函数相比,macros不需要为参数指定数据types。
总的来说,它们是编程的有用工具。 而macros指令和函数都可以根据情况使用。