C语言学习
十六.程序环境和预处理
1.翻译环境和运行环境
编译又分为三个阶段:
- 预编译(文本操作):将include引入的头文件展开成代码,并把注释删除,使用空格代替注释,替换#define的文本
- 编译:把c语言代码翻译成汇编代码(语法分析、词法分析、语义分析、符号汇总)
- 汇编:把汇编代码转换成二进制指令,形成符号表
链接:1.合并段表,2.符号表的合并和重定位
运行环境:
- 程序必须载入内存中,有操作系统的环境中:这个一般由操作系统完成。在独立的环境中,程序的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成
- 程序的执行然后开始,接着调用main函数
- 开始执行程序代码,这个时候程序将使用一个运行时堆栈,存储函数的局部变量和返回地址。 程序同时使用静态内存
- 终止程序
2.预定义符号
3.#define定义标识符
语法:#define 名字 值(可以加;,最好不加,容易报错)
这个值也可以是一段代码,如果值过长,可以分成几行写,除最后一行外,每行的后面都加一个\
#define定义宏
宏的声明:#define name(parament-list) stuff
parament-list是由,隔开的参数列表
注意:传参是完全替换,如果参数里写1 + 1,那传过去的就是1 + 1,解决方法是把X + X写成((X) + (X))
#define替换规则
①调用宏时,首先对参数进行检查,查看是否包含任何由#define定义的符号。如果是,则替换
②替换文本随后被插入到程序中原来文本的位置,对于宏,参数名被他们的值替换
③最后,再次对结果文件进行扫描,查看是否包含任何由#define定义的符号。如果是,则重复上述步骤
注意事项:
- 宏参数和#define定义中可以出现其它#define定义的变量。但是对于宏,不能出现递归
- 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不会被搜索
4.#和##
#define PRINT(X) printf("X say", X)
这段代码中,X是字符串中的字符,不会被替换
可以用#让参数插入到字符串中:
#define PRINT(X) printf(#X" say", X)
##的作用:可以把它两边的可以将两个宏参数连接成一个单一的标记
例子:
#define CONCAT(a, b) a ## b
int main() {
int xy = 10;
int x = 1;
int y = 2;
// 使用宏CONCAT来连接x和y,形成标识符xy
printf("%d\n", CONCAT(x, y)); // 输出: 10
return 0;
}
5.带有副作用的宏参数
当宏参数在宏的定义中出现超过一次的时候,如果参数带有副作用,那么这个宏的使用就存在风险
比如a++在宏中出现超过一次时,使用宏之后a会不止加一次1
6.宏和函数的对比
宏在调试时就被替换,所以效率高,宏是类型无关的
但是宏是无法调试的,不能递归的,不够严谨的,容易出错的
但是宏可以做到函数做不到的:宏的参数可以出现类型
宏的命名一般是全部大写,函数名全不大写
7.#undef
#undef 是一个预处理指令,用于取消定义一个预处理器符号(宏)。当你使用 #define 定义了一个宏之后,可以使用 #undef 来取消该宏的定义
8.命令行定义
在用命令行运行代码时,可以用命令行定义符号
如:gcc test.c -D SZ=5
9.条件编译
调试性的代码,删除可惜,保留又碍事,所以我们可以选择性的编译
常见的条件编译指令:
- #if 代码 #endif
- #if 代码 #elif 代码 #else 代码 #endif
- #if defined(符号)等价于#ifdef 变量名,意为如果符号被定义了(后面也要加#endif)
- #if !defined(符号)等价于#ifndef 变量名,意为符号没有被定义(后面也要加#endif)
- #if defined(符号) 代码 #elif defined 代码 #endif
10.文件包含
本地文件被包含:#include “文件名”
库文件包含:#include <文件名>
如果硬要使用 #include <> 来包含本地文件,编译器会在系统的标准头文件路径中查找该文件,而不是在当前源文件所在的目录中查找。因此,如果你的本地文件不在标准头文件路径中,编译器就会报错,无法找到该文件
文件嵌套包含
在引文件时可能会重复引用,为了避免这个问题可以用条件编译:
在每个文件的开头写:
或者:
#pragma once
11.其它预处理指令
详见官网