C语言宏定义浅析

动机

前几天看到一个面试题:写一个“标准”宏MIN,这个宏输入两个参数并返回较小的一个,里面提到两个题目考察点:

  1. 懂得在宏中小心地把参数用括号括起来
  2. 宏的副作用,例如:当你写下面的代码时会发生什么事? least = MIN(*p++, b);

平时也会用到宏定义,对于宏的理解还不特别深入,重新看了Kernighan的C程序设计语言中关于宏部分,特次记录.

宏定义的本质

#define 名字 替换文本

宏的本质是代码替换,也就是很长一个代码,起一个简单的名字来使用,在编译过程中会将这部分长代码替换简短的名字.

宏定义的优点与劣势

优点:

  1. 提高了程序的可读性,同时也方便进行修改;
  2. 提高程序的运行效率:使用带参的宏定义既可完成函数调用的功能,又能避免函数的出栈与入栈操作,减少系统开销,提高运行效率;
  3. 宏是由预处理器处理的,通过字符串操作可以完成很多编译器无法实现的功能。比如##连接符。

备注: ## 连接符号由两个井号组成,其功能是在带参数的宏定义中将两个子串(token)联接起来,从而形成一个新的子串。但它不可以是第一个或者最后一个子串。所谓的子串(token)就是指编译器能够识别的最小语法单元。加##就是为了把宏定义中的参数识别出来

劣势:

  1. 对带参的宏而言,由于是直接替换,并不会检查参数是否合法,存在安全隐患。因为 预编译语句仅仅是简单的值代替,缺乏类型的检测机制。这样预处理语句就不能享受C++严格的类型检查的好处,从而可能成为引发一系列错误的隐患。
  2. 嵌套定义过多可能会影响程序的可读性,而且很容易出错;
  3. 由于是直接嵌入的,所以代码可能相对多一点.

使用宏定义注意事项

1.宏中小心地把参数用括号括起来

为什么要这样做呢?括号很重要的作用就是规定计算次序.仅仅观察宏定义你知道计算顺序,但是把这个代码片段放到整个代码中,如果没有括号,原来预定的计算次序就会被打乱.例如:

#define square(x)     x * x

定义了一个求取一个数平安的宏定义,但是呢.如果用square(x+1)调用该宏定义会如何,通过完全代码完全替换后,编译时的代码是:x+1 * x + 1根本不是所预期的计算次序.那上面的宏定义如果改为:

#define square(x)   ((x) * (x))

用square(x+1)调用该宏定义时就完全没有问题了.最外面那层括号为了将宏定义整个运算和外界代码隔离开来.

2.避免宏的副作用

宏定义本真存在副作用,如果表达式中有个自增自减或输入/输出等,就会出错.如果用square(x++)调用上面的宏定义,那么最终运行的代码为((x++) * (x++))是自己预想的结果么?

[1].Kernighan B W, Ritchie D M, 徐宝文, 等. C 程序设计语言[M]. 机械工业出版社, 2004.
[2].C++/C宏定义中## 连接符与# 符的含义
[3].宏定义的优缺点

你可能感兴趣的:(编程语言)