文章目录
- 1.程序的翻译环境和执行环境
- 2.详解编译 + 链接
- 3.预处理详解
- 3.1 预定义符号
- 3.2 #define
- 3.3 宏和函数对比
- 3.4 #undef
1.程序的翻译环境和执行环境
在ANSI C的任何一种实现中,存在两个不同的环境。
第一种是翻译环境,在这个环境中源代码被翻译成可执行的机器指令。
第二种是执行环境,在这个环境中执行代码。
它们的关系如图所示

2.详解编译 + 链接
2.1 翻译环境

一个工程中存在多个源文件,每个源文件都是单独经过编译器处理之后生成一个目标文件(.obj文件)。
当生成多个目标文件之后,链接器会把多个目标文件和链接库链接在一起,生成可执行程序(.exe文件)
翻译过程如图

预处理阶段完成的事情
- 完成了头文件的包含
- #define定义的符号的替换
- 注释的删除
编译阶段完成的事情
把C语言代码转化为汇编代码
- 语法分析
- 词法分析
- 语义分析
- 符号汇总
汇编阶段完成的事情
把汇编代码转换为机器指令(二进制指令)
2.3 运行环境
- 程序必须载入内存中。在有操作系统的环境中,程序的载入由这个操作系统来完成。在独立的环境中,程序的载入必须由手工安排,也可以通过可执行代码置入只读内存来完成。
- 程序的执行开始,接着便调用main函数。
- 开始执行程序代码。这时程序会使用一个运行的堆栈(stack),用来保存函数的局部变量和返回值。同时程序也可以使用静态(static)内存,保存在静态内存中的变量的值在整个程序的执行过程中都不能改变。
- 终止程序。正常终止main函数,也有可能是异常终止。
3.预处理详解
3.1 预定义符号

3.2 #define
#define是定义符号的

如何将参数插入到字符串中?
这里两个双引号和一个双引号的作用是相同的。

如图所示,每次打印都要有printf很麻烦,有没有好点的方法?

可以用宏来实现,这里用到的原理是:当宏参数为字符串时它才可以插入到字符串中,而#可以把一个宏参数变成对应的字符串。

还可以再优化一下,使得传入什么类型的数据都可以

注:#不能单独用在字符串中,只能配合宏定义一起使用
##的作用:
将两个符号合成一个符号

3.3 宏和函数对比
宏的优点:
- 宏比函数在规模和速度上更省一筹
- 宏是类型无关的,而函数只能在相应的类型上使用。
- 宏的参数可以出现类型,而函数做不到。
宏的缺点
- 每次使用宏的时候,一份宏定义的代码插入到程序中,除非宏比较短,除非大幅度增加代码的长度。
- 宏不可调试
- 宏由于类型无关,也不可调试
- 宏可能带来运算符优先级的问题,导致容易出错
两者对比
- 代码长度: 每次使用宏时,宏代码都被插入到程序中,除非特别小的宏,否则程序的长度会大幅度增长。函数代码在同一个地方,每次调用函数,都使用同一个地方的同一份代码。
- 执行速度: 宏的执行速度较快。函数存在调用和返回的额外浪费,所以速度较慢。
- 操作符优先级: 宏使用在有表达式的上下文中,如果不加括号,可能会因为操作符的优先级而出现不可预料的结果,所以建议在使用宏时多加括号。函数的参数只在传参时计算一次,结果更稳定。
- 带副作用的参数: 宏参数可能被替换到宏体内的多个部分,如果参数带有副作用,可能产生不可预料的结果。函数参数的副作用只在传参时计算一次,易于预测结果
- 参数类型: 宏没有特定的参数类型,只要后续操作是合法的,那么就可以传入任何类型的参数。函数的参数类型是特定的,参数类型不一样,就需要使用不同的函数。
- 调试: 宏不可以调试。函数是可以逐语句调试的。
- 递归: 宏没有递归。函数有递归。
3.4 #undef
用于取消宏定义

3.5 条件编译
在编译一个程序的时候我们要将一个语句(一组语句)编译或者放弃是很方便的,因为我们有条件编译指令。
比如说:调试性的代码,删除可惜,保留又碍事,所以我们可以选择性的编译。

如果将PRINT定义为任意数字都可以打印


常见的条件编译指令:

