C语言预处理命令--宏定义

一、宏讲解

1、宏定义

       宏(Macro),是一种批量处理的称谓。计算机科学里的宏是一种抽象(Abstraction),它根据一系列预定义的规则替换一定的文本模式。解释器编译器在遇到宏时会自动进行这一模式替换。

2、C语言宏定义的常规用法

1) 定义符号常量

#define PI 3.1415926

#define MAX_N 10000

2) 定义傻瓜表达式(注意,定义的这种表达式一不小心很容易出现bug,下文会讲)

#define S(a, b) a * b

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

3) 定义代码段

#define P(a) {\
  printf("%d\n", a);\
} 

ps:编译器对于宏的解析是很严谨的,只能支持一行解析,\是起连接作用,表示当行的宏代码与下一行宏连接在一起,使得编译器当成一行看待。

3、编译器预定义的宏

在C语言中,我们有很多预定义的宏,就是C语言帮程序员预先定义好的宏,可以让我们使用。

宏                                       说明

__DATE__                            日期:Mmm dd yyyy

__TIME__                             时间:hh:mm:ss

__LINE__                              当前源文件的代码行号

__FILE__                               文件名

__func__                              函数名/非标准

__FUNC__                            函数名/非标准

__PRETTY_FUNCTION__      更详细的函数信息/非标准

4、预定义命令-条件式编译

函数                                     说明

#ifdef DEBUG                      是否定义了DEBUG宏

#ifndef DEBUG                    是否没有定义DEBUG宏

#if MAX_N == 5                  宏MAX_N是否等于5

#elif MAX_N == 4               否则宏MAX_N是否等于4

#else

#endif  

5、预定义命令

从上图可以看到:

  预编译

  将.c 文件转化成 .i文件

  使用的gcc命令是:gcc –E

  对应于预处理命令cpp

  编译

  将.c/.h文件转换成.s文件

  使用的gcc命令是:gcc –S

  对应于编译命令 cc –S

  汇编

  将.s 文件转化成 .o文件

  使用的gcc 命令是:gcc –c

  对应于汇编命令是 as

  链接

  将.o文件转化成可执行程序

  使用的gcc 命令是: gcc

  对应于链接命令是 ld

  总结起来编译过程就上面的四个过程:预编译、编译、汇编、链接。这里我们主要讲预编译阶段,不去细究其他阶段,具体细节可以去看编译原理的书本。

       预编译是做些代码文本的替换工作。
  处理以# 开头的指令 , 比如拷贝 #include 包含的文件代码,#define 宏定义的替换 , 条件编译等,就是为编译做的预备工作的阶段。
  主要处理#开始的预编译指令,预编译指令指示了在程序正式编译前就由编译器进行的操作,可以放在程序中的任何位置。
C 编译系统在对程序进行通常的编译之前,首先进行预处理。
  C 提供的预处理功能主要有以下三种:
1 )宏定义。
2 )文件包含。
3 )条件编译。

  而我们这里只讲预定义,也就是说,我们可以通过预编译生成的编译源码去看我们的宏替换后有没有符合我们的预期,下面会实际操作。

二、宏使用

  上文,我们知道了宏的三种用法,分别如下:

1) 定义符号常量

2) 定义傻瓜表达式

3) 定义代码段

例子一:

定义一个宏,表示一年有多少秒?

//seconds.c
#include <stdio.h>
#define SEC_OF_A_YEAR (365 * 24 * 60 * 60)

int main(void) {
    printf("%d\n", SEC_OF_A_YEAR);
    return 0;
}

我们对源码进行预编译操作,顺便去查看预编译后的结果,如下

ydq@ubuntu:macro$ gcc -E seconds.c  > seconds.txt
ydq@ubuntu:macro$ tail seconds.txt

# 2 "seconds.c" 2



# 4 "seconds.c"
int main(void) {
    printf("%d\n", (365 * 24 * 60 * 60));
    return 0;
}
ydq@ubuntu:macro$ 

可以看到预编译后,我们的宏SEC_OF_YEAR被替换成了(365 * 24 * 60 * 60),所以我们可以得知,宏定义在预编译后只是进行了简单的代码替换,这其实对于新手来说,是很危险的。

例子二:

请定义一个没有bug的MAX(a, b)宏,需要通过如下测试:

1、MAX(2, 3)

2、5 + MAX(2, 3);

3、MAX(2, MAX(3, 4))

4、MAX(2, 3 > 4 ? 3 : 4)

5、MAX(a++, 6)a的初值为7,宏返回值为7,a的值变为8。

验证1:

1 //max_version1.c 
2 #include <stdio.h>
3 #define MAX(a, b) a > b ? a : b
4 
5 int main(void) {
6     printf("MAX(%d, %d) = %d\n", 2, 3, MAX(2, 3));
7     return 0;
8 }
ydq@ubuntu:macro$ gcc max_version1.c
ydq@ubuntu:macro$ ./a.out 
MAX(2, 3) = 3

由结果得出验证通过,那么我们接着下来在该代码基础上,继续验证2.

1 //max_version1.c
2 #include <stdio.h>
3 #define MAX(a, b) a > b ? a : b
4 
5 int main(void) {
6     printf("MAX(%d, %d) = %d\n", 2, 3, MAX(2, 3));
7     printf("%d + MAX(%d, %d) = %d\n", 5, 2, 3, 5 + MAX(2, 3));
8     return 0;
9 }
ydq@ubuntu:macro$ gcc max_version1.c 
ydq@ubuntu:macro$ ./a.out 
MAX(2, 3) = 3
5 + MAX(2, 3) = 2

这时候我们发现验证2的时候结果错了,我们预编译出结果来看一下是怎么回事。

dq@ubuntu:macro$ gcc -E max_version1.c > max_version1.txt
ydq@ubuntu:macro$ tail max_version1.txt
# 2 "max_version1.c" 2

 

# 4 "max_version1.c"
int main(void) {
printf("MAX(%d, %d) = %d\n", 2, 3, 2 > 3 ? 2 : 3);
printf("%d + MAX(%d, %d) = %d\n", 5, 2, 3, 5 + 2 > 3 ? 2 : 3);
return 0;
}
ydq@ubuntu:macro$

由汇编源码看出5 + MAX(2, 3)被替换为5 + 2 > 3 ? 2 : 3,由于+运算符的优先级比>号运算符的优先级高,又是左结合,所以5 + 2 > 3 ? 2 : 3相当于是7 > 3 ? 2 : 3,故最终输出结果是为2。为了解决这个bug,我们可以通过把三目运算符的整个表达式给用括号括起来,让其优先级最高,如#define MAX(a, b) (a > b ? a : b),代码如下:

1 //max_version2.c
2 #include <stdio.h>
3 #define MAX(a, b) (a > b ? a : b)
4 
5 int main(void) {
6     printf("MAX(%d, %d) = %d\n", 2, 3, MAX(2, 3));
7     printf("%d + MAX(%d, %d) = %d\n", 5, 2, 3, 5 + MAX(2, 3));
8     return 0;
9 }

接着,编译测试:

ydq@ubuntu:macro$ gcc max_version2.c
ydq@ubuntu:macro$ ./a.out 
MAX(2, 3) = 3
5 + MAX(2, 3) = 8

现在,我们得出结果正确了。接着我们可以继续添加代码验证3。

 1 //max_version2.c
 2 #include <stdio.h>
 3 #define MAX(a, b) (a > b ? a : b)
 4 
 5 int main(void) {
 6     printf("MAX(%d, %d) = %d\n", 2, 3, MAX(2, 3));
 7     printf("%d + MAX(%d, %d) = %d\n", 5, 2, 3, 5 + MAX(2, 3));
 8     printf("MAX(%d, MAX(%d, %d)) = %d\n", 2, 3, 4, MAX(2, MAX(3, 4)));
 9     return 0;
10 }

编译验证结果:

ydq@ubuntu:macro$ gcc max_version2.c
ydq@ubuntu:macro$ ./a.out 
MAX(2, 3) = 3
5 + MAX(2, 3) = 8
MAX(2, MAX(3, 4)) = 4

我们发现,题目3也是正确的。那我们接着用这个版本的代码继续验证题目4。

 1 //max_version2.c
 2 #include <stdio.h>
 3 #define MAX(a, b) (a > b ? a : b)
 4 
 5 int main(void) {
 6     printf("MAX(%d, %d) = %d\n", 2, 3, MAX(2, 3));
 7     printf("%d + MAX(%d, %d) = %d\n", 5, 2, 3, 5 + MAX(2, 3));
 8     printf("MAX(%d, MAX(%d, %d)) = %d\n", 2, 3, 4, MAX(2, MAX(3, 4)));
 9     printf("MAX(%d, %d > %d ? %d : %d) = %d\n", 2, 3, 4, 3, 4, MAX(2, 3 > 4 ? 3 : 4));
10     return 0;
11 }

编译测试结果

ydq@ubuntu:macro$ gcc max_version2.c 
ydq@ubuntu:macro$ ./a.out 
MAX(2, 3) = 3
5 + MAX(2, 3) = 8
MAX(2, MAX(3, 4)) = 4
MAX(2, 3 > 4 ? 3 : 4) = 2

发现结果错了,理论上应该是4,结果却是2。继续用预编译命令验证。

ydq@ubuntu:macro$ gcc max_version2.c  -E > max_version2.txt
ydq@ubuntu:macro$ tail max_version2.txt


# 5 "max_version2.c"
int main(void) {
    printf("MAX(%d, %d) = %d\n", 2, 3, (2 > 3 ? 2 : 3));
    printf("%d + MAX(%d, %d) = %d\n", 5, 2, 3, 5 + (2 > 3 ? 2 : 3));
    printf("MAX(%d, MAX(%d, %d)) = %d\n", 2, 3, 4, (2 > (3 > 4 ? 3 : 4) ? 2 : (3 > 4 ? 3 : 4)));
    printf("MAX(%d, %d > %d ? %d : %d) = %d\n", 2, 3, 4, 3, 4, (2 > 3 > 4 ? 3 : 4 ? 2 : 3 > 4 ? 3 : 4));
    return 0;
}
ydq@ubuntu:macro$ 

我们MAX(2, 3 > 4 ? 3 : 4)被替换成(2 > 3 > 4 ? 3 : 4 ? 2 : 3 > 4 ? 3 : 4),因为2 > 3 > 4 的结果为0,所以2 > 3 > 4 ? 3 : 4为4,4为真,(2 > 3 > 4 ? 3 : 4 ? 2 : 3 > 4 ? 3 : 4)就为2,故打印出来的结果是为2。由此我们得出,一旦a和b是表达式的话,MAX(a, b)会有可能有bug,我们还是得用括号保护a和b,a和b若是表达式时能够优先级最大。下面我们给出代码

 1 //max_version2.c
 2 #include <stdio.h>
 3 #define MAX(a, b) ((a) > (b) ? (a) : (b))
 4 
 5 int main(void) {
 6     printf("MAX(%d, %d) = %d\n", 2, 3, MAX(2, 3));
 7     printf("%d + MAX(%d, %d) = %d\n", 5, 2, 3, 5 + MAX(2, 3));
 8     printf("MAX(%d, MAX(%d, %d)) = %d\n", 2, 3, 4, MAX(2, MAX(3, 4)));
 9     printf("MAX(%d, %d > %d ? %d : %d) = %d\n", 2, 3, 4, 3, 4, MAX(2, 3 > 4 ? 3 : 4));
10     return 0;
11 }

编译并运行验证。

ydq@ubuntu:macro$ gcc max_version3.c 
ydq@ubuntu:macro$ ./a.out 
MAX(2, 3) = 3
5 + MAX(2, 3) = 8
MAX(2, MAX(3, 4)) = 4
MAX(2, 3 > 4 ? 3 : 4) = 4

接下来我们验证最后的一个题目。

 1 //max_version2.c
 2 #include <stdio.h>
 3 #define MAX(a, b) ((a) > (b) ? (a) : (b))
 4 
 5 int main(void) {
 6     printf("MAX(%d, %d) = %d\n", 2, 3, MAX(2, 3));
 7     printf("%d + MAX(%d, %d) = %d\n", 5, 2, 3, 5 + MAX(2, 3));
 8     printf("MAX(%d, MAX(%d, %d)) = %d\n", 2, 3, 4, MAX(2, MAX(3, 4)));
 9     printf("MAX(%d, %d > %d ? %d : %d) = %d\n", 2, 3, 4, 3, 4, MAX(2, 3 > 4 ? 3 : 4));
10     int a = 7;
11     printf("MAX(a++, %d) = %d\n", 6, MAX(a++, 6));
12     printf("a = %d\n", a);
13     return 0;
14 }

编译并运行验证

ydq@ubuntu:macro$ gcc max_version3.c 
ydq@ubuntu:macro$ ./a.out 
MAX(2, 3) = 3
5 + MAX(2, 3) = 8
MAX(2, MAX(3, 4)) = 4
MAX(2, 3 > 4 ? 3 : 4) = 4
MAX(a++, 6) = 8
a = 9

原题要求 MAX(a++, 6)a的初值为7,宏返回值为7,a的值变为8。

但实际结果是宏的返回值时8,a的值变为了9。

这里我们继续用预编译去验证。

ydq@ubuntu:macro$ gcc  -E max_version3.c > max_version3.txt 
ydq@ubuntu:macro$ tail max_version3.txt
int main(void) {
    printf("MAX(%d, %d) = %d\n", 2, 3, ((2) > (3) ? (2) : (3)));
    printf("%d + MAX(%d, %d) = %d\n", 5, 2, 3, 5 + ((2) > (3) ? (2) : (3)));
    printf("MAX(%d, MAX(%d, %d)) = %d\n", 2, 3, 4, ((2) > (((3) > (4) ? (3) : (4))) ? (2) : (((3) > (4) ? (3) : (4)))));
    printf("MAX(%d, %d > %d ? %d : %d) = %d\n", 2, 3, 4, 3, 4, ((2) > (3 > 4 ? 3 : 4) ? (2) : (3 > 4 ? 3 : 4)));
    int a = 7;
    printf("MAX(a++, %d) = %d\n", 6, ((a++) > (6) ? (a++) : (6)));
    printf("a = %d\n", a);
    return 0;
}

解决方法如下:

 1 //max_version4.c
 2 #include <stdio.h>
 3 #define MAX(a, b) ({\
 4     __typeof(a) _a = (a);\
 5     __typeof(b) _b = (b);\
 6     _a > _b? _a : _b;\
 7 })
 8 
 9 int main(void) {
10     printf("MAX(%d, %d) = %d\n", 2, 3, MAX(2, 3));
11     printf("%d + MAX(%d, %d) = %d\n", 5, 2, 3, 5 + MAX(2, 3));
12     printf("MAX(%d, MAX(%d, %d)) = %d\n", 2, 3, 4, MAX(2, MAX(3, 4)));
13     printf("MAX(%d, %d > %d ? %d : %d) = %d\n", 2, 3, 4, 3, 4, MAX(2, 3 > 4 ? 3 : 4));
14     int a = 7;
15     printf("MAX(a++, %d) = %d\n", 6, MAX(a++, 6));
16     printf("a = %d\n", a);
17     return 0;
18 }

编译并运行测试

ydq@ubuntu:macro$ gcc max_version4.c 
ydq@ubuntu:macro$ ./a.out 
MAX(2, 3) = 3
5 + MAX(2, 3) = 8
MAX(2, MAX(3, 4)) = 4
MAX(2, 3 > 4 ? 3 : 4) = 4
MAX(a++, 6) = 7
a = 8

至此,我们已经测试并通过上述5个测试的MAX(a, b)宏,由此可见,运用宏去定义表达式要格外小心。

例子三,我们来实现一个打印宏P(func),实现调用P(MAX(2, 3))可以打印出MAX(2, 3) = 3。

 1 //max_version5.c
 2 #include <stdio.h>
 3 #define MAX(a, b) ({\
 4     __typeof(a) _a = (a);\
 5     __typeof(b) _b = (b);\
 6     _a > _b? _a : _b;\
 7 })
 8 
 9 #define P(func) {\
10     printf("%s = %d\n", #func, func);\
11 }
12 
13 int main(void) {
14     P(MAX(2, 3));
15     P(5 + MAX(2, 3));
16     P(MAX(2, MAX(3, 4)));
17     P(MAX(2, 3 > 4 ? 3 : 4));
18     int a = 7;
19     P(MAX(a++, 6));
20     P(a);
21     return 0;
22 }

#func表示将func字符串化,编译运行。

ydq@ubuntu:macro$ gcc max_version5.c 
ydq@ubuntu:macro$ ./a.out 
MAX(2, 3) = 3
5 + MAX(2, 3) = 8
MAX(2, MAX(3, 4)) = 4
MAX(2, 3 > 4 ? 3 : 4) = 4
MAX(a++, 6) = 7
a = 8

例子4:实现一个打印LOG的函数,需要输出所在函数及行号等信息

注:宏__FILE__以字符串形式返回所在文件名称

  宏__func__以字符串形式返回所在函数名称

  宏__LINE__以整数形式返回代码行号

 

 1 #include <stdio.h>
 2 
 3 #ifdef DEBUG
 4 #define LOG(frm, args...) {\
 5     printf("[%s : %s : %d] ", __FILE__, __func__, __LINE__);\
 6     printf(frm, ##args);\
 7     printf("\n");\
 8 }
 9 #else
10 #define LOG(frm, args...) { }
11 #endif
12 
13 int main(void) {
14     int a = 123;
15     int b = 456;
16     printf("[%s : %s : %d] %d\n", __FILE__, __func__, __LINE__, a);
17     LOG("a = %d, b = %d", a, b);
18     LOG("hello world!");
19     return 0;
20 }

编译并运行。

ydq@ubuntu:macro$ gcc log.c -DDEBUG
ydq@ubuntu:macro$ ./a.out 
[log.c : main : 16] 123
[log.c : main : 17] a = 123, b = 456
[log.c : main : 18] hello world!

  

 

  

 

 

posted @ 2020-11-02 22:19  ydqun  阅读(597)  评论(0编辑  收藏  举报