一,前言
从所周知,计算机是能够执行二进制指令的,但是我们写出的C语言代码是文本信息,计算机不能直接理解所以我们地代码是如何变成二进制代码从而能够让计算机执行的呢?
在ANSI C的任何一种实现中,存在两个不同的环境
第一种是翻译环境,在这个环境中源代码被转换为可执行的机器指令
第二种是执行环境,用于实际执行代码
二,程序的翻译环境
先看图
1,组成一个程序的每个源文件通过编译过程分别转化成目标文件
2,每个目标文件由链接器捆绑在一起,形成一个单一而完整的可执行程序
3,链接器同时也会引入标准C函数库中任何被该程序所包含的函数,而且可以搜素程序员个人的程序库,将其需要的函数也链接到程序中
由于VS2022不好演示代码翻译的各个阶段,所以下面的操作我们采用linux操作系统演示,不会使用Linux操作没关系,只需要看结果即可
三,预处理
预处理是C代码翻译的第一个阶段,这个阶段主要包括注释的删除,#define符号的替换,#include头文件的包含,并且执行所有的预处理指令。看下列代码
这是在Linux操作系统上使用vim编辑器写的代码,之后我们使用如下指令就可以使gcc编译器执行预处理指令后然后停下来
gcc test.c -E -o test.i
表示将test.c预处理后的结果放到test.i文件里
预处理完成后查看test.i
可以看到代码从原先的不到10行变成了800多行,这里由于限制只截图了最后的部分,这多出来的800行其实就是stdio.h头文件的内容,而且原先定义的宏M也被替换了,注释也没了
四,编译
预处理之后就是编译了,该环节主要讲C语言代码变成对应的汇编代码,而对于汇编代码,我们了解得很少很少,而且汇编代码学习起来成本极高,所以我们目前只需要知道编译是将C语言代码转化为汇编代码即可(语法分析,词法分析,语义分析,符号汇总 -- 《编译原理》)
使用如下指令即可
gcc test.c -S -o test.s
五,汇编与链接
这一步就是将上面得汇编代码转化为二进制指令,将test.s翻译成目标文件,目标文件里面放的都是二进制指令
gcc -c test.s -o test.o
会形成符号表,在链接期间会查看符号表,这部分我们到后面的“深度解析C语言”再说
gcc test.o -o test
链接之后形成可执行文件,主要做两件事:1,合并段表 2,符号表的合并和符号表的重定位,这部分我们也放到“深度解析C语言”再讲
六,ELF存储格式
(如果没有学习过Linux操作系统,这部分可以直接跳过)
我们先把test.c改一下
gcc test.c -c -o test.o
我们查看test.o时会看到全是乱码,因为test.o里面全是二进制数据
但是我们可以发现,在这一坨乱码的开头有三个字母'ELF', Linux下gcc编译产生的目标文件test.o以及可执行程序test都是按照ELF这种文件的格式来存储的,而且Linux中也有专门的工具来识别ELF的文件:readrlf
readelf test.o -a
a表示all,查看全部信息,可以根据需要指定查看内容
这里只展示一部分
我们可以用-s来查看符号
readelf test.o -s
虽然其他的我们看不懂,但是我们看右下角,那不就是我们在test.c中写的各种变量和函数名吗,这里先做铺垫,更详细的讲解我们到“深度解析C语言再讲解”
七,程序的执行环境
①程序必须载入到内存中,这个一般由操作系统完成;但是在独立的环境中没有操作系统,程序的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成
②程序的执行与开始,就是调用main函数
③开始执行程序代码,这个时候程序将使用一个运行时堆栈,存储函数的局部变量和返回地址。程序同时也可以使用静态内存,存储于静态内存中的变量在程序的整个执行过程中一直保留他们的值
④终止程序,或正常结束main函数,或因为意外终止