c语言长度宏定义运算符,C语言在宏定义中使用语句表达式和预处理器运算符

语句表达式的亮点在于定义复杂功能的宏。使用语句表达式来定义宏,不仅可以实现复杂的功能,而且还能避免宏定义带来的歧义和漏洞。下面以一个简单的最小值的宏为例子一步步说明。

1、灰常简单的么,使用条件运算符就能完成,不就是

#define MIN(x,y) x > y ? y : x

当然这是最基本的 C 语言语法,可以写一个测试程序,验证一下我们定义的宏的正确性

#include

#define MIN(x,y) x < y ? y : x

int main(int argc, const char **argv)

{

printf("min = %d\r\n", MIN(1,2));

printf("min = %d\r\n", MIN(2,1));

printf("min = %d\r\n", MIN(2,2));

printf("min = %d\r\n", MIN(3,1+1));

printf("min = %d\r\n", MIN(1!=1, 1!=2));return 0;

}

d98551567159bb2d206e50b5b109c6e6.png

当宏的参数是一个非简单的加减乘除的表达式时,发现实际运行结果为 min=1,和我们预想结果 min=0 不一样。这是因为,宏展开后(宏只做替换,不做运算),就变成了这个样子

printf("min = %d\r\n", 2!=2 > 1!=2 ? 1!=2 : 2!=2);

因为比较运算符 > 的优先级为6,大于 判断运算符!=(优先级为7),所以简单的宏替换的表达式展开后,运算顺序发生了改变,结果结果可想而知了。

2、通常为了避免这种展开错误,我们可以给宏的参数加一个小括号()来防止展开后,表达式的运算顺序发生变化。这样的宏才能算一个比较完善的宏:

#define MIN(x, y) (x) > (y) ? (y) : (x)

再一次进行测试,可以使用下面的代码测试:

#include

#define MIN(x, y) (x) > (y) ? (y) : (x)

int main(int argc, const char **argv)

{

printf("min = %d\r\n", 3 + MIN(1,2));

printf("min = %d\r\n", 4 + MIN(1!=2,2));return 0;

}

1b4db6a25f30c53e2dfbeaf705d15dcc.pngwAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==?

在程序中,我们打印表达式 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

#define MIN(x, y) ((x) > (y) ? (y) : (x))

int main(void)

{int i = 2;int j = 6;

printf("min = %d\r\n", MIN(i++, j++));return 0;

}

wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

81e31b065a13b82549acd409937faae0.pngwAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==?

实际运行结果发现 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的值,然后进行比较,这样就避免了两次自增、自减问题。

2bd9a2da4e98652922e9f91573a64ff2.pngwAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==?

5、在上面这个宏定义的两个临时变量数据类型是 int 型。那对于其它类型的数据,就需要重新再定义一个宏,但是可以基于上面的宏继续修改,让它可以支持任意类型的数据比较大小:

#include

#define MIN(type, x, y) ({\type _x=x; type _y=y; _x> _y ?_y : _x;})int main(void)

{int i = 2;int j = 6;

printf("min = %d\r\n",MIN(int, i++, j++));

printf("min = %.2f\r\n",MIN(float, 3.14, 3.15));return 0;

}

wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

在这个宏中,我们添加一个参数:type,用来指定临时变量 _x 和 _y 的类型。这样,我们在比较两个数的大小时,只要将2个数据的类型作为参数传给宏,就可以比较任意类型的数据了。

4c290fa8a2cd43af6130f35e056b5d8f.pngwAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==?

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

#define PRINT(a, b) \printf(#a"and" #b "\n")int main(void)

{

PRINT(Dog abc, Cat);return 0;

}

wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

394d02591cbaa82ebe023df10783a102.pngwAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==?

8、宏定义中标记粘贴运算符(##)

宏定义内的标记粘贴运算符(##)会合并两个参数。它允许在宏定义中两个独立的标记被合并为一个标记。例如:

#include

#define tokenpaster(n) \printf ("token" #n "= %d", token##n)int main(void)

{int token12 = 34;

tokenpaster(12);return 0;

}

wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

1f0d4a90cac9bd85612d31b01db9eccf.pngwAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值