C语言在宏定义中使用语句表达式和预处理器运算符
语句表达式的亮点在于定义复杂功能的宏。使用语句表达式来定义宏,不仅可以实现复杂的功能,而且还能避免宏定义带来的歧义和漏洞。下面以一个简单的最小值的宏为例子一步步说明。
1、灰常简单的么,使用条件运算符就能完成,不就是
#define MIN(x,y) x > y ? y : x
当然这是最基本的 C 语言语法,可以写一个测试程序,验证一下我们定义的宏的正确性
#include <stdio.h> #define MIN(x,y) x < y ? y : x int main(int argc, const char **argv)
{
printf("min = %d\r\n", MIN(,));
printf("min = %d\r\n", MIN(,));
printf("min = %d\r\n", MIN(,));
printf("min = %d\r\n", MIN(,+));
printf("min = %d\r\n", MIN(!=, !=)); return ;
}

当宏的参数是一个非简单的加减乘除的表达式时,发现实际运行结果为 min=1,和我们预想结果 min=0 不一样。这是因为,宏展开后(宏只做替换,不做运算),就变成了这个样子
printf("min = %d\r\n", != > != ? != : !=);
因为比较运算符 > 的优先级为6,大于 判断运算符!=(优先级为7),所以简单的宏替换的表达式展开后,运算顺序发生了改变,结果结果可想而知了。
2、通常为了避免这种展开错误,我们可以给宏的参数加一个小括号()来防止展开后,表达式的运算顺序发生变化。这样的宏才能算一个比较完善的宏:
#define MIN(x, y) (x) > (y) ? (y) : (x)
再一次进行测试,可以使用下面的代码测试:
#include <stdio.h> #define MIN(x, y) (x) > (y) ? (y) : (x) int main(int argc, const char **argv)
{
printf("min = %d\r\n", + MIN(,));
printf("min = %d\r\n", + MIN(!=,));
return ;
}

在程序中,我们打印表达式 3 + MIN(1, 2) 的值,预期结果应该是5,但实际运行结果却是2。我们展开后,发现同样有问题:
3 + (1) > (2) ? (2) : (1);
因为运算符 + 的优先级大于比较运算符 >,所以这个表达式就变为4>2?2:1,最后结果为2也就
释然了。
对于4 + (1!=2) > (2) ? (2) : (1!=2);
同样的道理,出现5>2?2:1,结果可想而知。
3、此时我们应该继续修改这个宏:
#define MIN(x, y) ((x) > (y) ? (y) : (x))
使用小括号将整个宏定义括起来,避免了当一个表达式同时含有宏定义和其它高优先级运算符时,破坏整个表达式的运算顺序。
那么现在的这个写法已经解决上面的两种写法带来的意外,在进行一下简单的测试。
#include <stdio.h> #define MIN(x, y) ((x) > (y) ? (y) : (x)) int main(void)
{
int i = ;
int j = ;
printf("min = %d\r\n", MIN(i++, j++));
return ;
}

实际运行结果发现 min = 3,而不是预期结果 min = 2。这是因为
((i++) > (j++)) ? (j++) : (i++);
在进行比较判断的时候i和j的值分别自加一次,然后在取结果的时候,i++又被执行了一次,所以,i的值变成了3。
4、基于上述的问题,可以使用语句表达式来定义这个宏。
注意:宏延续运算符(\)的概念
一个宏通常写在一个单行上。但是如果宏太长,一个单行容纳不下,则使用宏延续运算符(\)。
#define MIN(x, y) ({ \
int _x = x; \
int _y = y; \
_x > _y ? _y : _x;\
})
在语句表达式中定义两个临时变量,分别来暂储x和y的值,然后进行比较,这样就避免了两次自增、自减问题。

5、在上面这个宏定义的两个临时变量数据类型是 int 型。那对于其它类型的数据,就需要重新再定义一个宏,但是可以基于上面的宏继续修改,让它可以支持任意类型的数据比较大小:
#include <stdio.h>
#define MIN(type, x, y) ({\
type _x = x;\
type _y = y;\
_x > _y ? _y : _x;\
})
int main(void)
{
int i = ;
int j = ;
printf("min = %d\r\n",MIN(int, i++, j++));
printf("min = %.2f\r\n",MIN(float, 3.14, 3.15));
return ;
}
在这个宏中,我们添加一个参数:type,用来指定临时变量 _x 和 _y 的类型。这样,我们在比较两个数的大小时,只要将2个数据的类型作为参数传给宏,就可以比较任意类型的数据了。

6、在内核中,尤其是在内核的宏定义中,被大量的使用。使用语句表达式定义宏,不仅可以实现复杂的功能,还可以避免宏定义带来的一些歧义和漏洞。比如在 Linux 内核中,max_t 和 min_t 的宏定义,就使用了语句表达式:
#define min_t(type, x, y) ({ \
type __min1 = (x); \
type __min2 = (y); \
__min1 < __min2 ? __min1 : __min2; })
#define max_t(type, x, y) ({ \
type __max1 = (x); \
type __max2 = (y); \
__max1 > __max2 ? __max1 : __max2; })
7、宏定义中字符串常量化运算符(#)
在宏定义中,当需要把一个宏的参数转换为字符串常量时,则使用字符串常量化运算符(#)。在宏中使用的该运算符有一个特定的参数或参数列表。例如:
#include <stdio.h> #define PRINT(a, b) \
printf(#a " and " #b "\n") int main(void)
{
PRINT(Dog abc, Cat);
return ;
}

8、宏定义中标记粘贴运算符(##)
宏定义内的标记粘贴运算符(##)会合并两个参数。它允许在宏定义中两个独立的标记被合并为一个标记。例如:
#include <stdio.h> #define tokenpaster(n) \
printf ("token" #n " = %d", token##n) int main(void)
{
int token12 = ; tokenpaster(); return ;
}

C语言在宏定义中使用语句表达式和预处理器运算符的更多相关文章
- 宏定义中的##操作符和... and _ _VA_ARGS_ _
1.Preprocessor Glue: The ## Operator 预处理连接符:##操作符 Like the # operator, the ## operator can be used i ...
- C语言宏定义中的#和##的作用【转】
本文转载自:http://my.oschina.net/shelllife/blog/123202 在宏定义中#和##的作用是:前者将宏定义的变量转化为字符串:后者将其前后的两个宏定义中的两个变量无缝 ...
- define宏定义中的#,##,@#及\符号
define宏定义中的#,##,@#及\符号 在#define中,标准只定义了#和##两种操作.#用来把参数转换成字符串,##则用来连接两个前后两个参数,把它们变成一个字符串. 1.# (string ...
- 宏定义中使用do{}while(0)的好处 (转载)
宏定义中使用do{}while(0)的好处 #define MACRO_NAME(para) do{macro content}while(0) 的格式,总结了以下几个原因: 1,空的宏定 ...
- 【编程基础】C语言常见宏定义
我们在使用C语言编写程序的时候,常常会使用到宏定义以及宏编译指令,有的可能比较常用,有的可能并不是很常用,是不是所有的C语言宏定义以及宏指令你都清楚呢? 指令 用途详细介绍 # 空指令,无任何效果 # ...
- #define宏定义中## #@ # \ 符号使用
C/C++ 宏命令的神奇用法. 先看下面三条语句: #define Conn(x,y) x##y#define ToChar(x) #@x#define ToString(x) ...
- 零基础逆向工程16_C语言10_宏定义_头文件_内存分配_文件读写
#define 无参数的宏定义的一般形式为:#define 标识符 字符序列 如:#define TRUE 1 注意事项: 1.之作字符序列的替换工作,不作任何语法的检查 2.如果宏定义不当,错误要到 ...
- C语言可变参数在宏定义中的应用
在C语言的标准库中,printf.scanf.sscanf.sprintf.sscanf这些标准库的输入输出函数,参数都是可变的.在调试程序时,我们可能希望定义一个参数可变的输出函数来记录日志,那么用 ...
- C在宏定义中使用的语言可变参数
于C标准库的语言,printf.scanf.sscanf.sprintf.sscanf入输出函数,參数都是可变的.在调试程序时.我们可能希望定义一个參数可变的输出函数来记录日志,那么用可变參数的宏是一 ...
随机推荐
- 关于css 的兼容设置 ----笔记
在开发网页的时候,由于浏览器的差异,会造成css 代码需要写不同的样式,才能适配大部分浏览器,这很烦,但是又不得不做,我把其中用得比较多的那些,做了一个归纳,放在这里,开发的时候,直接打开这里,复制粘 ...
- html:布局class名和其他class名个人整理总结
一:app 二:header 三:main 四:footer 1:中横杠:布局(main-top.main-left等) 2:下横杠:状态(item-active:选中等)
- 二叉搜索树的第K大节点
题目描述 给定一颗二叉搜索树,请找出其中的第k大的结点. 分析 对二叉搜索树进行逆向中序遍历(先右再左),则遍历序列是降序排序的,因此中序遍历一颗二叉搜索树,可以很容易的得到它的第k大的节点.使用一个 ...
- 20175213 2018-2019-2 《Java程序设计》第6周学习总结
教材学习内容总结 1.第七章:内部类与异常类 ①.内部类和外嵌类之间的重要关系: ·内部类的外嵌类的成员变量在内部类中仍然有效,内部类中的方法也可以调用外嵌类中的方法. ·内部类的类体中不可以声明类变 ...
- UVALive 3942 Remember the Word
题意:给出一个由S个不同单词组成的字典和一个长字符串.把这个字符串分解成若干个单词的连接(单词可以重复 使用),有多少种方法? Sample Input abcd 4 a b cd ab Sample ...
- docker学习-lnmp+redis之搭建lnp容器服务
nginx+php7.0容器服务 本来想用单独的容器(nginx和php分开),但是因为是初学,php容器安装扩展的时候一直失败,所以就把centos+nginx+php放一起搭建了,优点是扩展简单, ...
- pythond的icmp广播报获取局域网主机IP
icmp广播报获取局域网四川特产IP from scapy.all import *import randomimport threading def scan(sip,dip): pkt = Eth ...
- innodb_flush_log_at_trx_commit与sync_binlog理解
innodb_flush_log_at_trx_commit该参数控制重做日志写入磁盘的过程.我们知道 InnoDB 使用“Write Ahead Log”策略来避免数据丢失问题,即依靠重做日志来保证 ...
- sql语句order by排序问题
根据某个字段的值排序,等于这个值的放最前面,不等的放后面 select * from [DTS_Interface] order by case when from_tag_name='木质饰条' t ...
- 《AlwaysRun!团队》第二次作业:团队项目选题报告
第二次作业:团队项目选题报告 项目 内容 这个作业属于哪个课程 https://www.cnblogs.com/nwnu-daizh/ 这个作业的要求在哪里 https://www.cnblog ...