目录
程序的翻译环境和执行环境
在 ANSI C(标准C) 的任何一种实现中,存在两个不同的环境。
*第1种是翻译环境。 在这个环境中源代码被转换为可执行的机器指令。
在编译器中(以VS2019为例),我们写的C语言代码都是文本的信息(各种字符串,数据,结构体等),站在人类的角度,我们能够理解,但是计算机不行,代码需要翻译成计算机呢能够识别的指令:二进制指令
***第2种是执行环境。**它用于实际执行代码。
代码经过翻译环境后生成的二进制指令代码,由执行环境来执行生成。
代码编译时生成可执行程序时的步骤:
1. 组成一个程序的每个源文件通过编译过程分别转换成目标代码( object code )。
2. 每个目标文件由链接器( linker )捆绑在一起,形成一个单一而完整的可执行程序。
3. 链接器同时也会引入标准 C 函数库中任何被该程序所用到的函数,而且它可以搜索程序员个人的程序库,将其需要的函数也链接到程序中。
简述来说:
每一个源文件(.c),都单独经过编译器进行编译,生成目标文件(.obj),目标文件和链接库再通过链接器的处理(这个过程就叫链接),就生成了可执行程序(.exe)。
如下图
翻译环境分为两部分,编译+链接
编译本身也分为几个阶段:预处理 , 编译 ,汇编。
接下来我们用一个简单的代码,源文件名(test.c),使用gcc这个编辑器,给大家演示整个过程;
第一步:预编译(预处理)
预编译的指令是:gcc 源文件.c -E - 源文件名 + .i
- 预处理 选项 gcc - E test.c - o test.i
预处理完成之后就停下来,预处理之后产生的结果都放在 test.i文件中,具体内容可看下图。
如下面的代码:
cpp源文件文件名 :test.c #include<stdio.h> //定义全局变量,赋值2023 int year = 2023; #define M 100 int main() { int a = M; printf("%d\n", M); return 0; }
1.输入指令gcc test.c -E -test i
终端输出:
2.观察test.c和test .i的内容
3.在test.i文件中查看
我们发现,在test.i整个代码的末尾,才是我们源代码的内容,那前面的几百行代码又是什么呢,是头文件<stdio.h>的整个内容包含进来了。同时细心的同学发现,test.i中没有了注释,也没有了宏定义的符号M了,所以预处理的作用是:
- 注释的删除
- #include<stddio.h>头文件的包含
- #define 符号的替换
- 文本操作
- 所以的预处理指令都是在预处理阶段处理的
第二步,编译
编译指令:gcc -S 源文件.c / gcc -S 源文件名 + .i
编译 选项 gcc - S test.c
编译 选项 gcc -S test.i (编译时,这两个文件都可以输入指令中,都会生成文件test.s)
编译完成之后就停下来,结果保存在 test.s 中。
我们输入 gcc - S test.c 为例
1.输入指令gcc -S test.c / gcc -S test.i
终端输出:
2.生成了test.s文件的内容并展示test.s文件的内容
3.总结:
上面黄圈部分就是test.s的内容,里面就是汇编指令。编译的作用是:把c语言代码翻译成汇编指令 。编译的方式是通过:语法分析,词法分析,语义分析,符号汇总等方式。总的来说,编译就是把我们的c语言代码拆解分析,然后翻译成汇编指令给下一步汇编的动作。
第三步:汇编
汇编指令:gcc -c 源文件.c / gcc -c 源文件名 + .s
汇编 gcc - c test.c
汇编完成之后就停下来,结果保存在 test.o 中。
1.输入指令gcc -c test.c / gcc-c test.s
终端输出:
2.生成了test.o的目标文件
但是你会发现这是test.o目标文件存放的二进制文件,编译器是不支持显示的的,如果仍要打开,如下图;
- 总结:
1.目标文件中存放的是二进制的指令
2.汇编是把汇编指令翻译成二进制指令
顺便提一下:Linux下gcc编译产生的目标文件test.o,可执行程序test都是按照ELF的这种文件格式存储的
最后,以上过程完成了编译,之后到链接过程
链接指令:gcc 源文件名.o -o 新文件名
对目标文件进行链接。可生成一个可执行文件
1.输入指令:gcc test.o -o vskkk
输出终端:
2,对vskkk的内容进行查看
但是文件还是二进制的指令无法查看。
3.总结:
链接的作用是:
1.合并线段
2.符号表的合并和重定位
关于运行环境分为四点:
运行环境相较于翻译环境就好理解的多了,通俗理解有以下几点:
- 程序必须载入内存中。在有操作系统的环境中:一般这个由操作系统完成。在独立的环境中,程序的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成。
- 程序的执行便开始。接着便调用 main 函数。
3.开始执行程序代码。这个时候程序将使用一个运行时堆栈(stack),存储函数的局部变量和回
地址。程序同时也可以使用静态( static )内存,存储于静态内存中的变量在程序的整个执行过程
一直保留他们的值。 - 终止程序。正常终止 main 函数;也有可能是意外终止。
关于链接库
我们知道再写C语言代码时,只要加上头文件 #include<stdio.h> ,就可以直接使用库函数了,比如scanf(),printf(),那为什么可以直接使用呢?原因是我们把这些库函数都已经提前编译好,打包好放在静态库中,可以直接提供给我们,比如我们熟悉的scanf函数,如下图的打红圈内容,标记部分后缀以LIB结尾的,就是静态库(.LIB),库函数的静态库和目标文件在链接器中发生链接生成可执行程序。程序就可以运行啦。
以上就是全部内容了,希望能帮助到大家,如果可以,也希望大家给博主点点赞支持一下,谢谢。如果有错误的地方,希望大家能在评论区批评指出,我会进行订正的。