宏定义式函数与普通函数

转自https://blog.csdn.net/wang664626482/article/details/52354229

在预处理时,对程序中所有出现的“宏名”,都用宏定义中的字符串去代换,这称为“宏代换”或“宏展开”。宏定义是由源程序中的宏定义命令完成的。宏代换是由预处理程序自动完成的。若字符串是表达式,我们称之为函数式宏定义。
我们以下面两行代码为例,展开描述:
函数式宏定义:#define MAX(a,b) ((a)>(b)?(a):(b))
普通函数 :MAX(a,b) { return a>b?a:b;}

(1)函数式宏定义的参数没有类型,预处理器只负责做形式上的替换,而不做参数类型检查,所以传参时要格外小心。

(2)函数式宏定义要注意格式,尤其是括号

若上面的宏定义式函数写成#define MAX(a,b) (a>b?a:b)省去内层括号,宏展开后由于运算符优先级,运行结果出错;若上面的宏定义式函数省去外层括号,宏定义为 ++MAX(a,b),则宏展开就成了 ++(a)>(b)?(a):(b),运算优先级也是错了。

(3)若函数参数为表达式,则普通函数的调用与函数式宏定义的替换过程是不一样的。

普通函数调用时先求实参表达式的值再传给形参,如果实参表达式有Side Effect,那么这些SideEffect只发生一次。例如MAX(++a, ++b),如果MAX是普通函数,a和b只增加一次。但如果MAX函数式宏定义,则要展开成k = ((++a)>(++b)?(++a):(++b)),a和b就不一定是增加一次还是两次了。所以若参数是表达式,替换函数式宏定义时一定要仔细看好。

(4)调用真正函数的代码和调用函数式宏定义的代码编译生成的指令不同。
如果MAX是个普通函数,那么它的函数体return a > b ? a : b; 要编译生成指令,代码中出现的每次调用也要编译生成传参指令和call指令。而如果MAX是个函数式宏定义,这个宏定义本身倒不必编译生成指令,但是代码中出现的每次调用编译生成的指令都相当于一个函数体,而不是简单的几条传参指令和call指令。所以,使用函数式宏定义编译生成的目标文件会比较大。

优势:
首先,函数调用会带来额外的开销,它需要开辟一片栈空间,记录返回地址,将形参压栈,从函数返回还要释放堆栈,这种开销会降低代码效率,而使用宏定义则在代码规模和速度方面比函数更胜一筹;
其次,函数的参数必须被声明为一种特定的类型,所以它只能在类型合适的表达式上使用,我们如果要比较两个浮点型的大小,就不得不再写一个专门针对浮点型大小的比较函数,反之,上面的宏定义可以用于整型、长整型、单浮点型、双浮点型及其他可以用“<”操作符比较值大小的类型,也就是说,宏与类型无关。

你可能感兴趣的:(C)