【C进阶】 |
您所在的位置:网站首页 › c代码转换成汇编代码怎么弄 › 【C进阶】 |
文章目录
1. 程序的翻译环境和执行环境2. 翻译环境详解2.1翻译环境介绍2.2 编译详解2.2.1 预处理(预编译)2.2.2 编译2.2.3 汇编
2.3 链接详解2.3.1 合并段表2.3.2 符号表的合并和重定位
3. 运行环境
这篇文章,我们来探讨一下,我们写的代码,是如何一步步变成可执行程序,最终运行得出结果的,一起来学习吧!!! 1. 程序的翻译环境和执行环境在ANSI C(美国国家标准协会(ANSI)及国际标准化组织(ISO)推出的关于C语言的标准)的任何一种实现中,程序都存在两个不同的环境。 第1种是翻译环境,在这个环境中源代码被转换为可执行的机器指令。 第2种是执行环境,用于实际代码执行。 也就是说: 我们写好的任何一个源代码,到最终产生结果,都要经历这两个环境。 比如,我们写好了一个test.c的源文件,它需要先经过翻译环境生成可执行程序test.exe,然后再经过执行环境产生最终的结果。 对于翻译环境呢,又分为编译和链接
那下面我们就在vs2022写一个代码,让大家粗略的感受一下编译和链接的这个过程: 那我们现在vs上对该程序生成解决方案: 然后: 链接器会把这些目标文件和链接库链接在一起,最终生成可执行程序。 那因为在test.c中使用了add.c中的add函数,所以这两个源文件要被链接在一起,这我们能想通。 那还有一个链接库,这是个什么鬼? 大家有没有注意到我们刚才的程序中还使用到了一个库函数——printf,像这些我们调用到的标准C函数库中的函数,就是放在链接库中的,链接器也会引入标准C函数库中这些被程序所用到的函数。 刚刚在上面的过程中我们提到了编译器和链接器这两个东西。 而对于我们平时写代码使用的这些工具,就比如我现在使用的这个vs2022,它其实不单单有编译和链接的功能,我们平时用的这些工具,它们都是一个集成开发环境(IDE),像常见的有 Visual Studio、Dev C++、Xcode、Visual C++ 6.0、C-Free、Code::Blocks 等。集成开发环境就是一系列开发工具的组合套装,比如编辑器,编译器,链接器,调式器等。 我们可以在上面编辑代码,编译和链接代码,以及调式代码等。 这个大家了解一下。 2.2 编译详解对于编译本身,又可以划分为3个阶段:预编译(预处理)、编译、汇编。 下面我们一起来看一下: 就还看上面那段代码,首先,大概的过程是这样的: 紧接着我们就来分析一下其中的细节: 注:接下来的大部分演示将在Linux环境下利用gcc进行,因为vs上面有些东西我们不好观察,所有有些操作大家不必关心,只要明白我们在干什么就行了。 当然这里面用到的一些命令大家可以了解一下: 预处理 选项 gcc -E test.c -o test.i 预处理完成之后就停下来,预处理之后产生的结果都放在test.i文件中。编译 选项 gcc -S test.c 编译完成之后就停下来,结果保存在test.s中。汇编 gcc -c test.c 汇编完成之后就停下来,结果保存在test.o中。然后我们写这样一段代码: 我们接下来对我们写的源文件test.c直接编译,然后生成了一个a.out的可执行程序,运行,我们看到成功打印了1到10的数字 但是我们刚刚直接完成了整个编译过程,并没有观察到其中的具体细节。 2.2.1 预处理(预编译)下面我们就分别观察一下其中的细节: 首先我们利用gcc -E test.c -o test.i让程序在预编译(预处理)之后停下来,并把内容输出到test.i文件中: 那为什么多了这么多内容呢? 大家有没有注意到我们在代码的第一行就包含了一个头文件stdio.h,那test.i中八百多行的内容中,在我们写的代码之前的那一大部分的内容是不是都是头文件带进来的内容。 是的,预编译之后的test.i中前面的那么多内容都是来自头文件stdio.h的内容。 我们可以验证一下,我们就打开一下stdio.h看看它里面的内容(具体操作大家不必关心): 那从这里我们就能够得出一个结论: 在预编译阶段需要做的事情之一是头文件的包含这件事。 那我们继续探讨一下,预处理阶段还会做其它哪些事情呢? 我们现在对刚才的代码做一些修改: 所以,我们就知道,在预编译阶段还做了: 注释的删除 #define定义的符号的替换 当然,肯定还不止这些事情,我们现在只是大致了解一下,下一篇文章我们会给大家详细介绍预处理。 2.2.2 编译那我们接下来就来研究一下编译阶段会发生什么? 还是这段代码: 那在转换的过程中,又会做什么呢? 1.语法分析 2.词法分析 3.语义分析 4.符号汇总 那这几步又是干什么呢? 大家如果不知道也没关系,不重要,不过这里我们需要去了解一下符号汇总。 那接下来,我们就了解一下符号汇总 我们再来写这样一段代码: 那我们怎么查看a.out这个文件呢? 我们去直接打开的话是不行的: 所以: 符号汇总其实就会把我们程序中的这些全局变量,函数名这种符号给汇总起来。 那这其实就是符号汇总的一个作用,为什么要单独解释一下符号汇总呢? 因为在链接的部分我们需要用到这些知识。 2.2.3 汇编那接下来就是汇编了,编译的最后一步。 那经过汇编之后,编译结束,是不是就产生对应的目标文件了呢 ? 是的。 那我们现在执行相关的命令让它在汇编之后停止:
所以: 汇编这一步做的其实就是把汇编指令转化为二进制的机器指令。 而生成的目标文件test.o其实也是elf格式的,我们打开她也能看到相关的符号:
通过上面的学习,我们知道,整个编译过程完成后,会产生目标文件,然后链接器就要对这样目标文件进行链接了。 那链接过程又会发生什么呢? 链接过程是将多个目标文件(可重定位目标文件)以及库文件组合在一起,生成最终的可执行文件。 主要过程有: 1. 合并段表 2. 符号表的合并和重定位 当然肯定不止这些,比如上面我们提到链接器会引入标准C函数库中任何被该程序所用到的函数,而且它可以搜索程序员个人的程序库,将其需要的函数也链接到程序中,最终生成可执行程序等,这里我们重点介绍这两个。 2.3.1 合并段表那什么是合并段表呢? 我们上面提到过生成的目标文件test.o其实也是elf格式的,而按照这个格式呢,会把文件分成一个一个的段,分别用来存放表示不同用途的数据。 那就拿我们最开始在vs上写的那个代码来说:
那符号表的合并和重定位又是什么呢? 我们已经知道了在汇编阶段会生成符号表,这些符号往往是一些全局变量,函数名等和它们对应地址的映射。 我们还来看这段代码: 那这两个文件最后要生成一个可执行文件呀,所以就需要对它们的符号表进行合并。 那合并的时候就会有一个问题,两个文件中都有一个add符号,地址应该选哪一个呢? 选add.c中的,为什么? 因为函数add.c在test.c中只是声明了一下,而真正的函数add的实现是在add.c中的,所以,最终要选择add.c中函数add的地址作为最终add的地址: 那这些东西有什么用处呢? 当链接过程中进行了符号表的合并和重定位之后,test.c中main函数调用add的时候是不是就能通过符号表中重定位之后的有效的函数地址找到add函数并调用它。 当然如果add.c中没有定义add函数,或者函数名我们写错的情况下,是不是也会因为符号表中没有有效的信息而报错。 我们可以验证一下,相信大家也遇到过这种情况: 最后,我们来了解一下一个程序执行的过程: 程序必须载入内存中。在有操作系统的环境中:这个过程一般由操作系统完成。在独立的环境中,程序的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成。程序的执行便开始。接着便调用main函数。开始执行程序代码。这个时候程序将使用一个运行时堆栈(stack),存储函数的局部变量和返回地址。程序同时也可以使用静态(static)内存,存储于静态内存中的变量在程序的整个执行过程一直保留他们的值。终止程序。正常终止main函数;也有可能是意外终止。这篇文章,我们比较笼统的介绍了一个程序从编译到链接,再到最后执行的过程,下一篇文章,我们将详细的介绍一下预处理过程。 这篇文章就到这里,希望能帮助到大家,也欢迎大家指正!!! |
今日新闻 |
推荐新闻 |
CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3 |