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支持的squaremacros,你可以这样做…

 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指令和函数都可以根据情况使用。