在分析一些C源码时,经常会遇到各种宏定义操作,本文即总结一下C语言宏定义中常见的预定义宏、调试宏;宏的条件编译用法及特殊的宏关键字用法。
文章目录
- #undef 限定宏的作用域
- 常见预定义宏
- 宏的条件编译
- #ifdef 和 #ifndef
- #if 用法
- #line
- #error
- # 用法
- ## 用法
- #pragma 用法
#undef 限定宏的作用域
一般来讲宏的作用域从 #define 开始直到文件末尾,但如果需要限定宏的作用域就可以用 #undef 来限定宏的作用域,就是该宏只能作用与 #define … #undef 区间内
常见预定义宏
预定义宏也称为编译器内置宏,这些宏并没有定义在哪个头文件中,而是编译器在预处理时会自动处理这些预定义宏,这里介绍几个常用的预定义宏,其中__func__、__FILE__、__LINE__三个宏在调试时经常用到,来定位bug,为了方便可以自己定义一个DEBUG宏: #define DEBUG printf("%s %s %d\n", __FILE__, func, __LINE__);
- __DATE__:代表预处理的日期,预处理器会将其替换为"月 日 年"的字符串形式的时间
- __TIME__:代表对源文件当前预编译的时间,预处理器会将其替换为“hh:mm:ss”格式的时间
- __FILE__:代表当前预编译的源文件文件名,预处理器会将其替换为当前宏所在的文件名
- __LINE__:代表当前所在行号,预处理器会将其替换为当前宏所在的行号
- __func__:会替换为当前__func__所在的函数名
如下代码:
void test_1() {
printf("预编译的日期:%s\n", __DATE__);
printf("预编译的时间:%s\n", __TIME__);
printf("预编译的文件:%s\n", __FILE__);
printf("当前所在行号:%d\n", __LINE__);
printf("当前所在函数:%s\n", __func__);
}
执行测试结果:
E:\C_demo>Chong.exe
预编译的日期:Jan 29 2020
预编译的时间:16:54:24
预编译的文件:e:\C_demo\Chong.c
当前所在行号:18
当前所在函数:test_1
宏的条件编译
所谓条件编译,也就是在预编译时,预编译器可以通过 “条件编译” 帮你保留某些代码、以及帮你去掉某些代码,第二阶段编译时就只编译保留的代码,条件编译可以包含任意内容,可以是 #include 头文件、整个函数、或者函数内部的代码块、变量定义、或是整个文件的内容(一般头文件中比较多)。条件编译在Linux驱动代码以及一些跨平台软件代码中非常常用,针对不同的硬件环境选择性编译不同的代码块来提高代码的兼容性。下面列出常见的条件编译用法的例子:
#ifdef 和 #ifndef
#ifdef 如果定义了该宏,就编译对应的 #endif 之间的代码块;#ifndef 如果没有定义该宏,就编译对应的 #endif 之间的代码块
#define A
#define C
void test_2() {
#ifdef A
printf("This is A.\n");
#endif
printf("This is B.\n");
#ifndef C
printf("This is C.\n");
#endif
}
执行测试代码:
E:\C_demo>Chong.exe
This is A.
This is B.
#if 用法
void test_3() {
#if 0 // 表达式为假
printf("条件为假,不会预编译这行代码\n");
#endif
printf("预编译这行代码\n");
}
如果 #if 表达式为真,预编译保留中间代码,为假时,则不保留中间代码
执行该代码如下:
E:\C_demo>Chong.exe
预编译这行代码
#if 常常会和 defined、#elif、#else 搭配使用
- #if defined : 功能与 #ifdef 是一样的,这时通过宏存不存在来判断真假,与 #ifdef 不同,#if define 可以实现宏的 “&&”、 “||” 运算
- #if !defined :与 #if defined 刚好相反
#define A
#define B
void test_4() {
#if defined A && defined B
printf("如果 A 和 B 都定义\n");
#endif
#if !defined A || !defined B
printf("如果没有定义 A 或者没有定义 B");
#endif
}
执行测试代码如下:
E:\C_demo>Chong.exe
如果 A 和 B 都定义
- #elif 和 #else:else if defined,否则如果定义该宏为真,保留该代码;#else 否则,保留以下代码
void test_5() {
#if 0
printf("#if 条件为真,保留这句代码\n");
#elif defined A
printf("如果定义了A,保留这句代码\n");
#else
printf("如果以上两个都不满足,保留这段代码\n");
#endif
}
执行测试代码结果如下:
E:\C_demo>Chong.exe
如果以上两个都不满足,保留这段代码
#line
可以根据自己的需求,修改__LINE__和__FILE__的值,也就是修改行号和文件名,并且修改的值永久生效。如下测试代码:
void test_6() {
printf("修改文件名和行号之前:%s: %d\n", __FILE__, __LINE__);
#line 100 "test.c"
printf("修改文件名和行号之后:%s: %d\n", __FILE__, __LINE__);
}
执行以上测试代码结果:
E:\C_demo>Chong.exe
修改文件名和行号之前:e:\C_demo\Chong.c: 65
修改文件名和行号之后:test.c: 100
#error
在Linux驱动移植中,往往会修改源码中的宏定义比较多,因为条件编译需要这些宏来打开和关闭,所以在预编译阶段如果就能准确的提示缺少某个宏的话,可以帮助我们快速排查宏的错误。
- #error 能够帮助我们在“预编译”阶段,及时准确的报出与该宏相关的错误并且退出预编译;
- #error是在“预编译阶段”由预编译器处理的“预编译关键字”;
- #error输出字符串时,信息内容不需要使用括号括起来;
- 执行#error后,“预编译”的处理过程会被立即终止并报错;
void test_7() {
#ifdef PI
printf("%d\n", PI);
#else
#error PI not defined!
#endif
}
当 PI 没有定义时,编译器编译以上代码会打印报错,提示该宏没有定义:
Executing task: D:\mingw-w64\mingw64\bin\gcc.exe -g e:\C_demo\Chong.c -o Chong.exe <
e:\C_demo\Chong.c: In function ‘test_6’:
e:\C_demo\Chong.c:66:2: error: #error PI not defined!
#error PI not defined!
^~~~~
The terminal process terminated with exit code: 1
# 用法
将 # 传入的参数变成字符串,如下代码
#define S1(s) #s
#define S2(s) #s" Friend"
#define S3(s) "Hello "#s
void test_8() {
printf("%s\n", S1(Hello Friend));
printf("%s\n", S2(Hello));
printf("%s\n", S3(Friend));
}
执行以上代码,输出三个 “Hello Friend”,通过预编译后查看代码也能够看出,该宏会将参数转换成字符串
## 用法
将两个标识符连接在一起,合成一个标识符。还是一样举个例子说明
#define MERGE(a, b) a##b
#define PRINT1 printf("print one\n");
#define PRINT2 printf("print two\n");
void test_9() {
MERGE(PRINT, 1); // 宏替换后是 PRINT1;
MERGE(PRINT, 2);
}
#undef MERGE
执行以上代码结果如下:
E:\C_demo>Chong.exe
print one
print two
#pragma 用法
- #pragma 的作用: 通过#pragma指定的某些设置,然后通过这些设置告诉编译器在预编译或者编译时,完成某些特定的事情。
- #pragma 的特点:预编译关键字都是在“预编译”阶段被处理的,预编译之后就看不到了,但是 #pragma 就不一定了,#pragma的大多数用法是在预编译阶段处理的,但是有些少数情况是在第二阶段“编译”时处理的。
- #pragma 的使用格式:#pragma parameter
- #pragma 的参数表:
*表示仅C++支持,其它的C/C++都支持
alloc_text | comment | init_seg* | optimize | auto_inline | component
inline_depth | pack | bss_seg | data_seg | inline_recursion | pointers_to_members*
check_stack | function | intrinsic | setlocale | code_seg | hdrstop
message | vtordisp* | const_seg | include_alias | once | warning
- #pragma once:与#ifndef一样,可以用于防止头文件的重复包含
- #pragma message(message string):在编译时打印提示信息,不是在预编译
- #pragma pack:内存对齐
- bss_seg、data_seg、code_seg、const_seg参数:
1> bss_seg:修改和设置.bss节
2> data_seg:修改和设置.data节
3> const_seg:修改和设置.ro.data节
4> code_seg:修改和设置.text节
关于一些常用的宏定义操作就先记录到此了,对于 #pragma 宏的使用,之后有接触更多用法再作更新…
Caso_卡索