不知道大家有没有想过这样一个问题,我们写完一个C语言代码,我们的计算机是怎么去识别的,然后输出我们想要的结果,今天我就和大家一起学习这个问题。
程序的编译
我们从写下一个.c文件到计算机输出我们想要的结果一共要经历两个环境:
- 翻译环境
- 执行环境
翻译环境
在翻译环境中源代码会被转换成可执行的机器指令。而转换成机器指令需要以下几个步骤:
- 预编译(预处理)
预处理主要有:头文件的包含,注释的删除,#define符号的替换。 - 编译
这个阶段就会把C语言的源代码转换成汇编代码,而转换的时候计算机会对我们的代码进行语法分析、词法分析、语义分析、符号汇总。如果大家想对编译这块了解的更深的话可以看看《编译原理》。 - 汇编
汇编会把汇编语言转换成二进制指令,我们的计算机只能读懂二进制语言。在windows环境下会生成.obj文件,而Linux环境下会生成.o文件。 - 链接
每个源文件都会生成一个目标文件,然后统一经过链接器生成可执行程序,windows下后缀为.exe文件
运行环境
- 程序必须载入内存中。在由操作系统的环境中:一般这个由操作系统完成。在独立的环境中,程序的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成。
- 程序的执行便开始。接着调用main函数。
- 开始执行程序代码。这个时候程序将使用一个运行时的堆栈(stack),存储函数的局部变量和返回地址。程序同时也可以使用静态(static)内存,存储于静态内存中的变量在程序的整个执行过程一直保留他们的值。
- 终止程序。正常终止main函数,也有可能是意外终止。
程序的预处理
预定义符号
FILE 进行编译的源文件
LINK 源文件中的行号
DATE 文件被编译的日期
TIME 文件被编译的时间
STDC 如果编译器遵循ANSI C ,值为1,否则未定义
本人用的是vs2019这个编译器不完全遵循ANSI C。
define
这个是一个宏定义的标识符,举个例子:
#define MAX 100
#define reg refister
#define mul(x) x*x
#define mul(x) (x)*(x)
注意:宏定义后面不要带分号
宏定义会在函数预处理阶段就替换成值或者表达式;例如:
#define mul(x) x*x
mul(5)
会被替换成 5*5
2*(5+mul(2+3))//这个表达式的结果是多少?是60吗?答案是32,因为会替换成2*(5+2+3*2+3)
所以用宏定义的时候需要格外注意,尽量带括号。
undef
这条指令用于移除一个宏定义。例如:
#define MAX 100
int main() {
printf("%d\n", MAX);
#undef MAX
printf("%d\n", MAX);
return 0;
}
这样MAX就会被取消定义。
条件编译
在C语言中有如下一些这样的指令,例如:
#if
#endif
#if
#elif
#else
#endif
#if defined(symbol)
#ifdef symbol
这些都是一些预编译的条件指令,话不多说直接上代码:
#define FLAG 1
int main() {
#if FLAG
printf("hello");
#endif
return 0;
}
这段代码预定义了一个FLAG这个表示符,而#if FLAG就是判断FLAG有没有被定义,如果有就执行下面代码,一直到#endif结束。
定义了FLAG就会打印hello,没有就不会打印,#if和#endif是成对出现的,不能漏掉任何一个。
而下面这个跟if else用法类似,但是不能混为一谈
就是那个条件满足执行那个,如果都不满足就执行#else后面的代码,最后也是以#endif结束。这些条件编译的预处理指令在实际开发中用到的也是非常多的,这是一个重点,我们在今后的学习中再进行更深入的了解。