文章目录
- 一.前言
- 二.编译环境
- 2.1预处理阶段
- 2.2编译阶段
- 2.3汇编阶段
- 2.4链接阶段
- 三.运行环境
一.前言
在ANSI C的任何一种实现中,存在两个不同的环境
1.翻译环境,在这个环境中源代码被转换为可执行的机器指令.
2.执行环境,它用于实际执行代码.
二.编译环境
我们每写完一个C语言代码,在编译的时候都会先经过编译环境把它变成可执行程序。
但是编译环境还要分成几个步骤:
预处理(预编译),编译,汇编,链接
这四个步骤需要用到两个工具才能实现:编译器,链接器
预处理–汇编阶段都叫编译。
当然我们再写代码的时候,不可能只有一个.c文件。再写多个.c文件后在编译:
每个.c文件通过编译器形成相对应的目标文件,然后所有的目标文件+链接库通过链接器形成可执行程序。
这些.c文件,目标文件在我们创建的工程里都是可以看到的
在运行.c的过程虽然很复杂,但是我们有很多可以用的IDE,比如我现在用的VS2022。
可能会有人想什么是IDE?IDE是集成开发环境。
我们刚刚提到的编译器(el.exe),链接器(link.exe),还有编辑器,调试器。我们用到的IDE就是帮我们把这些封装在了一起,我们调试,运行的时候直接用就行。
2.1预处理阶段
我们程序在预编译之后会发生什么呢?
- 头文件的包含。我们再写代码的时候,像stdio.h,stdlib.h这些头文件肯定都会用到。所以在预编译之后,就把这些头文件里的内容全部放进去。
- 注释的消除。我们在写代码的时候会有写注释的习惯,我们也知道这些注释,是根本不会被运行的,那是因为注释在预编译的时候已经消掉了。
- #define符号的替换。我们用到的宏定义也会被替换到程序中。
2.2编译阶段
编译阶段是把C语言代码转换成汇编代码。
然后在进行几个步骤:
语法分析
词法分析
语义分析
符号汇总
这里我主要带你们认识一下符号汇总:
每个.c文件都或多或少用到函数,变量。
符号汇总就是把每个.c程序里用到的函数名,全局变量名都各自汇总在一起。这就是符号汇总。
2.3汇编阶段
- 把汇编指令转化成二进制指令
- 形成符号表
什么是符号表?
我们在编译的环节有一个符号汇总,就是把所有函数名,全局变量名字放到一起。
符号表,你可以认为是这个样子的
就是每个文件都会有这样的一个表。具体有什么用,一会在链接的地方就可以体现出来了
这里有个要注意的点,text.c和Add.c都有Add.c函数,但是在Add.c文件中Add函数是有一个固定的地址的。但是在text.c文件里我们只是声明了一下,地址也是随机的。
2.4链接阶段
- 合并段表
- 符号表的合并和重定位
合并段表
我们刚才讲过在编译阶段结束后会生成一个.obj的目标文件(linux环境下生成的是.o文件)。这个目标文件它是elf文件格式的。
就是它这个文件里的内容是分成几个部分存放的:
合并段表,就是把每个.c文件形成的.obj文件各自的部分全部合成一起,形成一个新的文件
符号表的合并和重定位:
在汇编阶段,每个文件都会形成一个符号表,但是我们text.c里的Add只是声明了一下,地址也是随机的,没有意义。
所以我们在合并的时候会重定位,将Add.c里的Add函数当成新的符号表的内容。
合并好之后,程序就可以在运行的时候通过各自函数的地址来找到对应的函数。
如果我们没有Add这个函数会怎么样?
你看就会报一个错误无法解析外部符号,而且是从LNK2019这里报的,就是说在链接这块出现了问题。
我们看:
发现在链接的过程中好像还有一个东西–>链接库。
链接库就是我们引入的外部的那些函数所用到的库,我们把链接库也一起添加上才能知道我们用到的函数的具体位置。
三.运行环境
程序执行的过程:
- 程序必须载入内存中。在有操作系统的环境中:一般这个由操作系统完成。在独立的环境中,程序的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成,像单片机,你就需要把写好的代码烧到单片机里去。
- 程序的执行便开始。接着便调用main函数。
- 开始执行程序代码。这个时候程序将使用一个运行时堆栈(stack),存储函数的局部变量和返回地址。程序同时也可以使用静态(static)内存,存储于静态内存中的变量在程序的整个执行过程一直保留他们的值。关于这一步想要了解的可以看看这个->函数栈帧的创建与销毁。
- 终止程序。正常终止main函数;也有可能是意外终止。