II.C语言本质21预处理2宏定义

第21章预处理2.宏定义较大的项目都会用大量的宏定义来组织代码,你可以看看/usr/include下面的头文件中用了多少个宏定义。看起来宏展开就是做个替换而已,其实里面有比较复杂的规则,C语言有很多复杂但不常用的语法规则本书并不涉及,但有关宏展开的语法规则本节却力图做全面讲解,因为它很重要也很常用。2.1.函数式宏定义以前我们用过的#defineN20或#defineSTR"hello,world"这种宏定义可以称为变量式宏定义(Object-likeMacro),宏定义名可以像变量一样在代码中使用。另外一种宏定义可以像函数调用一样在代码中使用,称为函数式宏定义(Function-likeMacro)。例如编辑一个文件main.c:#defineMAX(a,b)((a)>(b)?(a):(b))k=MAX(i&0x0f,j&0x0f)我们想看第二行的表达式展开成什么样,可以用gcc的-E选项或cpp命令,尽管这个C程序不合语法,但没关系,我们只做预处理而不编译,不会检查程序是否符合C语法。$cppmain.c#1"main.c"#1"<built-in>"#1"<command-line>"#1"main.c"k=((i&0x0f)>(j&0x0f)?(i&0x0f):(j&0x0f))就像函数调用一样,把两个实参分别替换到宏定义中形参a和b的位置。注意这种函数式宏定义和真正的函数调用有什么不同:1、函数式宏定义的参数没有类型,预处理器只负责做形式上的替换,而不做参数类型检查,所以传参时要格外小心。2、调用真正函数的代码和调用函数式宏定义的代码编译生成的指令不同。如果MAX是个真正的函数,那么它的函数体returna>b?a:b;要编译生成指令,代码中出现的每次调用也要编译生成传参指令和call指令。而如果MAX是个函数式宏定义,这个宏定义本身倒不必编译生成指令,但是代码中出现的每次调用编译生成的指令都相当于一个函数体,而不是简单的几条传参指令和call指令。所以,使用函数式宏定义编译生成的目标文件会比较大。3、定义这种宏要格外小心,如果上面的定义写成#defineMAX(a,b)(a>b?a:b),省去内层括号,则宏展开就成了k=(i&0x0f>j&0x0f?i&0x0f:j&0x0f),运算的优先级就错了。同样道理,这个宏定义的外层括号也是不能省的,想一想为什么。4、调用函数时先求实参表达式的值再传给形参,如果实参表达式有SideEffect,那么这些SideEffect只发生一次。例如MAX(++a,++b),如果MAX是个真正的函数,a和b只增加一次。但如果MAX是上面那样的宏定义,则要展开成k=((++a)>(++b)?(++a):(++b)),a和b就不一定是增加一次还是两次了。5、即使实参没有SideEffect,使用函数式宏定义也往往会导致较低的代码执行效率。下面举一个极端的例子,也是个很有意思的例子。例21.1.函数式宏定义#defineMAX(a,b)((a)>(b)?(a):(b))inta[]={9,3,5,2,1,0,8,7,6,4};intmax(intn){returnn==0?a[0]:MAX(a[n],max(n-1));}intmain(void){max(9);return0;}这段代码从一个数组中找出最大的数,如果MAX是个真正的函数,这个算法就是从前到后遍历一遍数组,时间复杂度是Θ(n),而现在MAX是这样一个函数式宏定义,思考一下这个算法的时间复杂度是多少?尽管函数式宏定义和真正的函数相比有很多缺点,但只要小心使用还是会显著提高代码的执行效率,毕竟省去了分配和释放栈帧、传参、传返回值等一系列工作,因此那些简短并且被频繁调用的函数经常用函数式宏定义来代替实现。例如C标准库的很多函数都提供两种实现,一种是真正的函数实现,一种是宏定义实现,这一点以后还要详细解释。函数式宏定义经常写成这样的形式(取自内核代码include/linux/pm.h):#definedevice_init_wakeup(dev,val)\do{\device_can_wakeup(dev)=!!(val);\device_set_wakeup_enable(dev,val);\}while(0)为什么要用do{...}while(0)括起来呢?不括起来会有什么问题呢?#definedevice_init_wakeup(dev,val)\device_can_wakeup(dev)=!!(val);\device_set_wakeup_enable(dev,val);if(n>0)device_init_wakeup(d,v);这样宏展开之后,函数体的第二条语句不在if条件中。那么简单地用{...}括起来组成一个语句块不行吗?#definedevice_init_wakeup(dev,val)\{device_can_wakeup(dev)=!!(val);\device_set_wakeup_enable(dev,val);}if(n>0)device_init_wakeup(d,v);elsecontinue;问题出在device_init_wakeup(d,v);末尾的;...

1、当您付费下载文档后,您只拥有了使用权限,并不意味着购买了版权,文档只能用于自身使用,不得用于其他商业用途(如 [转卖]进行直接盈利或[编辑后售卖]进行间接盈利)。
2、本站不对文档的完整性、权威性及其观点立场正确性做任何保证或承诺!文档内容仅供参考,付费前请自行鉴别。
3、如文档内容存在侵犯商业秘密、侵犯著作权等,请点击“举报”。

常见问题具体如下:

1、问:已经付过费的文档可以多次下载吗?

      答:可以。登陆您已经付过费的账号,付过费的文档可以免费进行多次下载。

2、问:已经付过费的文档不知下载到什么地方去了?

     答:电脑端-浏览器下载列表里可以找到;手机端-文件管理或下载里可以找到。

            如以上两种方式都没有找到,请提供您的交易单号或截图及接收文档的邮箱等有效信息,发送到客服邮箱,客服经核实后,会将您已经付过费的文档即时发到您邮箱。

注:微信交易号是以“420000”开头的28位数字;

       支付宝交易号是以“2024XXXX”交易日期开头的28位数字。

客服邮箱:

biganzikefu@outlook.com

所有的文档都被视为“模板”,用于写作参考,下载前须认真查看,确认无误后再购买;

文档大部份都是可以预览的,笔杆子文库无法对文档的真实性、完整性、准确性以及专业性等问题提供审核和保证,请慎重购买;

文档的总页数、文档格式和文档大小以系统显示为准(内容中显示的页数不一定正确),网站客服只以系统显示的页数、文件格式、文档大小作为依据;

如果您还有什么不清楚的或需要我们协助,可以联系客服邮箱:

biganzikefu@outlook.com

常见问题具体如下:

1、问:已经付过费的文档可以多次下载吗?

      答:可以。登陆您已经付过费的账号,付过费的文档可以免费进行多次下载。

2、问:已经付过费的文档不知下载到什么地方去了?

     答:电脑端-浏览器下载列表里可以找到;手机端-文件管理或下载里可以找到。

            如以上两种方式都没有找到,请提供您的交易单号或截图及接收文档的邮箱等有效信息,发送到客服邮箱,客服经核实后,会将您已经付过费的文档即时发到您邮箱。

注:微信交易号是以“420000”开头的28位数字;

       支付宝交易号是以“2024XXXX”交易日期开头的28位数字。

确认删除?