较大的C语言项目都会用大量的宏定义来组织代码,比较经典的代码就是Linux Kernel的头文件中用的宏定义。看起来宏展开就是做个替换而已,其实里面有比较复杂的规则,有关宏展开的语法规则此小结力图整理的比较全面。

Object-like Macro

#define N 20

#define STR "hello, world"

上面源码所示的被称为Object-like Macro.

Function Macro

#define MAX(a, b) ((a)>(b)?(a):(b))

k = MAX(i&0x0f, j&0x0f)

上面源码所示的被称为Function-like Macro,注意这种函数式宏定义和真正的函数调用有什么不同:

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

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

定义这种宏要格外小心,如果上面的定义写成#define MAX(a, b) (a>b?a:b),省去内层括号,则宏展开就成了k = (i&0x0f>j&0x0f?i&0x0f:j&0x0f),运算的优先级就错了。同样道理,这个宏定义的外层括号也是不能省的,想一想为什么。

调用函数时先求实参表达式的值再传给形参,如果实参表达式有Side Effect,那么这些Side Effect只发生一次。例如MAX(++a, ++b),如果MAX是个真正的函数,a和b只增加一次。但如果MAX是上面那样的宏定义,则要展开成k = ((++a)>(++b)?(++a):(++b)),a和b就不一定是增加一次还是两次了。

即使实参没有Side Effect,使用函数式宏定义也往往会导致较低的代码执行效率。

尽管函数式宏定义和真正的函数相比有很多缺点,但只要小心使用还是会显著提高代码的执行效率,毕竟省去了分配和释放栈帧、传参、传返回值等一系列工作,因此那些简短并且被频繁调用的函数经常用函数式宏定义来代替实现。例如C标准库的很多函数都提供两种实现,一种是真正的函数实现,一种是宏定义实现,这一点以后还要详细解释。函数式宏定义经常写成这样的形式(取自内核代码include/linux/pm.h):

#define device_init_wakeup(dev,val) \

do { \

device_can_wakeup(dev) = !!(val); \

device_set_wakeup_enable(dev,val); \

} while(0)

当我第一次看到这个源码的时候,很奇怪为什么都用do{...}while(0)将函数式宏定义括起来。下面我们来分析分析如果不用while括起来会发生什么事情,如下源码所示:

/* main.c */

#define device_init_wakeup(dev,val) \

{ device_can_wakeup(dev) = !!(val); \

device_set_wakeup_enable(dev,val); }

int main(int argc, char *argv[]) {

if (n > 0)

device_init_wakeup(d, v);

else

continue;

return 0;

}

接下来可以用gcc -E main.c来查看预处理之后的源码,

int main(int argc, char *argv[]) {

if (n > 0)

/*device_init_wakeup(d, v);*/

{device_can_wakeup(d) = !!(v);

//; leads to get the end of the if condition and get error

device_set_wakeup_enable(d, v);};

else

continue;

return 0;

}

宏定义替换之后可以看出分号导致if条件判断提前结束,else分支找不到对应的if分支,这样就会导致无法正确编译。

另外,需要注意的是如果在一个程序文件中重复定义一个宏,C语言规定这些重复的宏定义必须一模一样。例如这样的重复定义是允许的:

#define OBJ_LIKE (1 - 1)

#define OBJ_LIKE /* comment */ (1/* comment */-/* comment */ 1)/* comment */

#define OBJ_LIKE (1-1)

/* 用#undef取消原来的定义,再重新定义 */

#define X 3

... /* X is 3 */

#undef X

... /* X has no definition */

#define X 2

... /* X is 2 */

Operator # and ##

在函数式宏定义中,#运算符用于创建字符串,#运算符后面应该跟一个形参(中间可以有空格或Tab),例如:

#define STR(s) # s

STR(hello world)

用cpp命令预处理之后是”hello␣world”,自动用”号把实参括起来成为一个字符串,并且实参中的连续多个空白字符被替换成一个空格。

在宏定义中可以用##运算符把前后两个预处理Token连接成一个预处理Token,和#运算符不同,##运算符不仅限于函数式宏定义,变量式宏定义也可以用。例如:

#define CONCAT(a, b) a##b

CONCAT(con, cat)

预处理之后是concat。我们知道printf函数带有可变参数,函数式宏定义也可以带可变参数,同样是在参数列表中用…表示可变参数。例如:

#define showlist(...) printf(#__VA_ARGS__)

#define report(test, ...) ((test)?printf(#test):\

printf(__VA_ARGS__))

showlist(The first, second, and third items.);

report(x>y, "x is %d but y is %d", x, y);

/* 宏定义*/

printf("The first, second, and third items.");

((x>y)?printf("x>y"): printf("x is %d but y is %d", x, y));

Like this:

Like Loading...

Related

c语言里宏定义算变量嘛,C语言宏定义的一些总结相关推荐

  1. db2 c语言游标名称可以是变量,mysql c语言 游标能取多行吗

    满意答案 xuyingcxm 2015.02.03 采纳率:45%    等级:12 已帮助:7182人 1. 无返回结果语句,如:INSERT,UPDATE,DROP, DELETE等 2. sel ...

  2. c语言规定对使用的变量必须,C语言为什么要规定对所用到的变量要“先定义,后使用”...

    该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 int a=10; 以上一句话对变量a进行了声明,定义以及初始化 extern int a; 以上一句话仅仅对变量a进行了声明,将a的链接属性设置为ext ...

  3. C语言里的4种存储类别,C语言中变量储存的类别

    C语言中变量储存的类别 C语言根据变量的生存周期来划分,可以划分为静态存储方式和动态存储方式. C语言中变量储存的类别 静态存储方式:是指在程序的运行期间分配固定的存储空间的方式.静态存储区中存储放了 ...

  4. C语言sfr定义一个变量,单片机c语言的sbit和sfr

    1.bit和sbit都是C51扩展的变量类型. bit和int char之类的差不多,只不过char=8位, bit=1位而已.都是变量,编译器在编译过程中分配地址.除非你指定,否则这个地址是随机的. ...

  5. c语言用指针访问简单变量,关于C语言指针,个人认为最经典、最简单的一个应用...

    上大学的时候,老师总会向我们灌输一个概念,C语言的精髓是"指针". 粗浅地理解,指针也是一个变量,和其他类型的变量没什么本质的区别.只不过,他存储的变量是一个"有类型的& ...

  6. c语言结构体结构体变量名,C语言结构体及结构体变量

    一.结构体类型的定义 结构体是一种新数据类型,属构造类型,它由若干类型各异的"成员"组成:描述这些"成员"可以使用任何基本数据类型,甚至是另外一种构造数据类型都 ...

  7. java定义基础变量语句_java语言基础-变量

    一丶变量的基本概念 1.什么是变量 (1).内存中的一个存储区域 (2).该区域有自己的名称(变量名),和类型(数据类型) (3.)该区域的数据可以在同一类型范围内不断变化(定义变量的主要目的是因为数 ...

  8. java 定义抽象变量_Java抽象类和抽象方法定义与用法实例详解

    本文实例讲述了Java抽象类和抽象方法定义与用法.分享给大家供大家参考,具体如下: 一.Java抽象类 1.抽象类的说明 在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都 ...

  9. c语言里的fun是什么函数,c语言fun函数有什么作用

    c语言fun函数的作用是被主函数所调用,来定义一个函数或方法,这样在引用时可以用fun表示,比如[int fun(int x,int y)]. c语言fun函数的作用是: C语言中,fun函数通常被主 ...

最新文章

  1. Android基于mAppWidget实现手绘地图(四)--如何附加javadoc
  2. c++强大还是python强大-2020,你该学习Python还是C++
  3. SBO中流程控制功能的实现-SBO_SP_TransactionNotification
  4. 从市场的角度来看技术的学习
  5. Log4Net简单使用
  6. 还有那个bspider不知道哪里的飞鸽传书
  7. bat实现监测计算机网络连接,断网自动重启网络连接
  8. 【文本摘要】BottleSum——文本摘要论文系列解读
  9. LINUX查看显卡信息
  10. 水木周平戏说中国网络黑幽默!
  11. tms sparkle创建server以及module实例
  12. 在docker中运行自己的eureka服务端
  13. 初学01-夜神模拟器连接Android Studio
  14. DOM初探(14)——查看滚动条的滚动距离
  15. 咸鱼前端—CSS浮动
  16. 堡垒机的主要功能是什么?为什么需要堡垒机?
  17. 如何高效的进行版本管理,版本管理的方法
  18. 智能制造系统解决方案和智能工厂发展趋势
  19. ubuntu 安装WINE
  20. 云师大的计算机师范专业好吗,云南除了云南师范大学,还有这些实力不错的师范学校...

热门文章

  1. 云计算在未来一年的发展预测
  2. 【前端微服务化】使用飞冰搭建前端微服务化框架
  3. http://blog.sina.com.cn/s/blog_ad1c3bdf0102uz99.html
  4. 英雄联盟全球总决赛历届冠军名单
  5. OTP动态密码_Java代码实现
  6. prompt learning
  7. zynq嵌入式linux显示logo,如何定制嵌入式linux 启动logo(小企鹅)
  8. Dual Illumination Estimation for Robust Exposure Correction阅读札记
  9. 联想IBM服务器X3650M3之硬盘扩容
  10. vb UBound 数据上界