[学习资料] 宏定义的陷阱

[复制链接]
 楼主| febgxu 发表于 2023-5-26 16:00 | 显示全部楼层 |阅读模式
(1)不能忽视宏定义中的空格

我们看宏定义: #define f(x) ((x) - 1)  假如我们不小心写成 #define f (x) ((x) - 1) ,此时编译是无法通过的,编译器把f 替换成了(x) ((x) - 1),因此宏定义时不能忽视空格,但是宏调用时却没有关系:

对于上面的宏定义 #define f(x) ((x) - 1) ,我们可以这样来调用:

  1. void main()

  2. {

  3.     int a = 10;

  4.     int result = f    (a);

  5.     printf("result: %d\n", result);

  6. }

(2)宏定义时不能吝啬括号

我们常常看到如下的宏定义

  1. #define abs(a) ((a) >=0 ? (a) : -(a))

在宏定义中,使用了大量的括号,这些括号的作用是预防引起与优先级有关的问题,

假如我们把abs定义成:

1
  1. #define abs(a) a >=0 ? a : -a

这个宏定义在运算abs(-10)时是没有问题的,但假设我们有如下宏调用:


  1. void main()

  2. {

  3.     int a = 10;

  4.     int b = 20;

  5.     int result_abs = abs(a - b);



  6.     printf("result_abs: %d\n", result_abs);

  7. }

程序输出的结果是: result_abs: -30

我们把宏展开就能看出其中的端倪  abs(10 - 20) // 10 - 20 >=0 ? 10 - 20 : - 10 - 20  ,这里的表达式 -10 - 20并没有按照我们所期望的 - (10 - 20)展开,所以造成了上面的错误,

我们再来看另一个宏定义: #define sum(a, b) a + b

当我们有如下宏调用时就会出错 int result_sum = sum(3, 5) * sum(2, 3); 因为宏展开变成了 3 + 5 * 2 + 3,由于*优先级高于+,所以程序输出 result_sum: 16

我们将宏改为 #define sum(a, b) ((a) + (b)) 就可以避免出现上面的错误。

因此我们在宏定义是要做的是在每一个参数以及整体表达式都加上括号

(3) "++" "--"在宏定义时引发的错误

即使我们在每个参数及整体表达式都加上括号,"++", "--"引发的错误仍然难以避免

我们定义一个max宏: #define max(a, b) ((a) > (b) ? (a) : (b))

有如下的调用:


  1. void main()

  2. {

  3.     int arr_number[3] = { 10, 30, 20 };

  4.     int biggest = arr_number[0];

  5.     int i = 1;

  6.     while (i < 3)

  7.     {

  8.          biggest = max(biggest, arr_number[i++]);

  9.     }

  10.     printf("The biggest number in array is %d\n", biggest);

  11. }

最后程序的输出是: bigest number in array is 20 为什么会输出20呢?我们期望的是30。

我们来分析下程序运行的过程:

bigest初始化为10,进入while循环,i = 1,运算表达是 bigest = max(bigest, arr_number[i++]); 我们将这里的max宏展开,

上面的表达式等价于

biggest = ((biggest) > (arr_number[i++]) ? (biggest) : (arr_number[i++]))
我们发现i自增了两次,下一次循环进入时i = 3,跳出了循环,输出的20是最后一次自增前的值,即arr_number[2].

这里的解决办法有两个 (a) 将max定义为函数, (b)宏调用时不使用 i++。

(4)宏不是类型定义

为了增加程序的可移植性,我们会如下定义宏:

  1. #define Student1 struct student1{ \

  2.     char id[5]; \

  3.     char name[10];\

  4.     int age;\

  5.     char sex[2];\

  6. }

我们也可以用typedef来定义一个新的类型:

  1. typedef struct student2

  2. {

  3.     char id[5];

  4.     char name[10];

  5.     int age;

  6.     char sex[2];

  7. } student2;

这两种命名方式看似差不多,但是typedef的方式更通用一些,考虑下面的代码:

  1. #define ST_P1 struct student1*

  2. typedef student2 *ST_P2;



  3. void main()

  4. {

  5.     ST_P1 stp1_a, stp1_b;

  6.     ST_P2 stp2_a, stp2_b;

  7. }

当我们试图用ST_P1和ST_P2来声明多个变量时,问题就来了,

stp1_b并不是指向student1结构的指针变量,而是student结构变量,我们把宏展开看看就很清楚:

  1. struct student1 *stp1_a, stp1_b

这个语句a被定义为指向结构的指针,而b被定义为结构

因此我们最好用typedef来定义类型,而不是#define

gaoyang9992006 发表于 2023-6-6 09:06 | 显示全部楼层
这可不是什么陷阱,这是自己不小心手误。
tpgf 发表于 2023-6-8 17:10 | 显示全部楼层
我们在定义宏定义的时候,会不会把括号也定义进去呢
caigang13 发表于 2023-6-8 18:07 来自手机 | 显示全部楼层
宏定义要小心括号使用
木木guainv 发表于 2023-6-9 08:38 | 显示全部楼层
其实只要严格把握好宏定义的定义 就行 只不过有时候很容易受到迷惑
磨砂 发表于 2023-6-9 09:21 | 显示全部楼层
宏定义写在函数的花括号外边,作用域为其后的程序,通常在文件的最开头
晓伍 发表于 2023-6-9 09:41 | 显示全部楼层
字符串" "中***不包含宏,并且宏定义不分配内存,变量定义分配内存。
八层楼 发表于 2023-6-9 10:27 | 显示全部楼层
宏的哑实结合不存在类型,也没有类型转换
观海 发表于 2023-6-9 10:43 | 显示全部楼层
宏展开不占运行时间,只占编译时间,函数调用占运行时间(分配内存、保留现场、值传递、返回值)
您需要登录后才可以回帖 登录 | 注册

本版积分规则

55

主题

5044

帖子

2

粉丝
快速回复 在线客服 返回列表 返回顶部