【计算机组成原理】读书笔记第四期:从源文件到可执行文件

目录

写在开头

从源代码到本地代码

源代码 

本地代码的初级形态(这一节有点不严谨)

编译器

从目标文件到可执行文件

启动和库文件

DLL文件及导入库 

可执行文件的运行机制

变量和函数的内存地址

程序加载时生成栈和堆

结尾 

写在开头

  本文继续阅读总结《程序是怎样跑起来的》这本书(作者:矢泽久雄)。前三篇博客介绍了这本书的阅读感受,并分别对第一章CPU、第四章内存相关、第五章磁盘的知识进行了总结。详情见: 

【计算机组成原理】读书笔记第一期:对程序员来说CPU是什么-CSDN博客

【计算机组成原理】读书笔记第二期:使用有棱有角的内存_Bossfrank的博客-CSDN博客 

【计算机组成原理】读书笔记第三期:内存和磁盘的关系_Bossfrank的博客-CSDN博客 

 本文将介绍本书的第八章:从源文件到可执行文件。重点描述从程序员编写的源程序代码开始,到可运行的.exe文件的过程(限于篇幅,本文仅介绍由.c源代码到.exe可执行文件的示例),涉及到的重点包括源代码和本地代码的概念、编译器的职责、形成可执行文件的过程可执行文件运行时的内存变化等。

从源代码到本地代码

  源代码是由高级语言编写的程序,是无法直接运行的。无论是哪种高级编程语言,最终都要翻译转换成本地代码(机器语言,即由二进制组成文件),才能被CPU所理解。换句话讲,即使是不同的编程语言编写的代码,最终转换成计算机可理解的本地代码后,都相当于变成了同一种语言(机器语言)了。如图8-2所示:

源代码 

  用某种高级编程语言编写的程序即成为源代码,保存源代码的文件称为源文件。源文件就是简单的文本文件,用任何文本编辑器都可以编写。用扩展名区分编程语言的种类,比如".c"就是C语言编写的源文件扩展名。代码8-1 sample1.c是一段用C语言编写的Windows程序,用于求取123和456的平均值并显示在消息框种,本篇即以这个程序sample1.c为例子,sample1c就是源代码。

//代码8-1 源文件sample1.c,求取123和456的平均值并在消息框中显示
#include <windows.h>
#include <stdio.h>// 消息框的标题
char* title = " 示例程序1";// 返回两个参数的平均值的函数
double Average(double a, double b) {return (a + b) / 2;
}// 程序运行启始位置的函数
int WINAPI WinMain(HINSTANCE h, HINSTANCE d, LPSTR s, int m)
{double ave; // 保存平均值的变量char buff[80]; // 保存字符串的变量// 求解123,456 的平均值ave = Average(123, 456);// 编写显示在消息框中的字符串sprintf(buff, " 平均值 = %f", ave);// 打开消息框MessageBox(NULL, buff, title, MB_OK);return 0;
}

  这个程序最终对应的可执行程序运行后执行结果如下: 

本地代码的初级形态(这一节有点不严谨)

  本地代码就是计算机可理解的内容,本质是二进制信息的罗列,每个二进制数值都表示某一个命令或者数据,由sample1.c源代码最终转换得到的可执行本地代码sample1.exe的本地代码如下图8-4:(备注:仅就我个人的阅读感受,作者这里关于本地代码和可执行文件搞得有点混乱了。后文又说本地代码是不能直接运行的,还需要链接过程才能形成可执行的.exe文件,或者也可以说可执行文件也是本地代码的最终形式,本节理论上讨论的应该是初级的本地代码,即目标文件sample1.obj才对,这里读者就当目标文件和可执行文件都是本地代码好了,图8-4我感觉有点不太合适,应该是对sample1.obj做dump才更合适,不过无论是sample1.obj还是sample1.exe都是二进制数值的罗列,数据和指令都对应二进制数值。)

 《程序是怎样跑起来的》这本书介绍用Dump方式查看本地代码,Dump是指把文件的内容,每个字节用2位十六进制数来表示的方式。 我们也可以用010Editor这样的工具直接查看。而计算机就是把所有的信息作为数值的集合来处理的,对于本地代码而言,数据和指令都是数值的罗列

这里注意一下:本地代码 可执行文件 的关系

应该说目标文件sample1.obj和最终的可执行文件sample1.exe都是本地代码,目标文件是初级的本地代码,可执行文件是最终形态的本地代码,本小节讨论的应该是目标文件这种本地代码,但给出的图8-4却是可执行性文件。我感觉作者稍微有点不严谨了。

编译器

 编译器是用于将高级编程语言编写的源代码转换成本地代码(目标文件)的程序

 编译器和解释器的不同:编译器是在运行前对所有源代码进行解释处理的。而解释器则是在运行时对源代码的内容一行一行地进行解释处理的。

编译器的三个关键词:

编程语言:每个编写源代码的编程语言都需要其专用的编译器。将C语言编写的源代码转换成本地代码的编译器称为C编译器。

CPU种类:跟据CPU类型的不同,本地代码的类型也不同,因此编译器的种类和CPU的类型也是相关的。同样的源代码通过不同的编译器,就可以翻译成适用于不同CPU的本地代码了(见图8-5)。

操作系统:为编译器本身也是程序的一种,所以也需要运行环境,即操作系统的种类(Windows/Linux等)。

 编译器的三要素类型举例如下图8-6(不过现在的编译器已经不需要专门购买了,已经默认集成在IDE集成开发环境中了)。

备注:有的编译器可以通过选择“Build”菜单来生成EXE文件。这种情况下,Build 指的是连续执行编译和链接,这种情况相当于一步到位直接生成了可执行文件。

从目标文件到可执行文件

  一图胜千言,见图8-8:

  sample1.c经过编译器编译后得到了目标文件sample1.obj,这个目标文件虽然是本地代码,但无法直接运行,这是因为当前程序还处于未完成状态。 源代码中并没有记述sprintf(),MessageBox()函数的内容,因此必须将存储着sprintf() 和MessageBox() 的处理内容的目标文件同Sample1.obj 结合,否则处理就不完整,EXE文件也就无法完成。将多个目标文件结合,生成一个可执行文件exe文件的过程就是链接,运行链接的程序就是链接器

启动和库文件

  启动:c0w32.obj这个目标文件记述的是同所有程序起始位置相结合的处理内容,称为程序的启动。因而,即使程序不调用其他目标文件的函数,也必须要进行链接,并和启动结合起来。

  库文件:像import32.lib及cw32.lib这样的文件称为库文件。 库文件指的是把多个目标文件集成保存到一个文件中的形式。链接器指定库文件后,就会从中把需要的目标文件抽取出来,并同其他目标文件结合生成EXE文件。

  静态链接库:像cw32.lib这样的库文件,存储着目标文件的实体,直接与exe文件相结合的库文件形式称为静态链接库。

使用库文件的好处:

1.简化了为链接器的参数指定多个目标文件这一过程。在链接调用了数百个标准函数的程序时,就要在链接器的命令行中指定数百个目标文件,这样就太繁琐了。而利用存储着多个目标文件的库文件的话,则只需在链接器的命令行中指定几个库文件就可以了。

    sprintf() 的目标文件位于cw32.lib中,像sprintf()这样的函数,不是通过源代码形式而是通过库文件形式和编译器一起提供的。这样的函数称为标准函数

2.对于软件开发公司来说,通过以目标文件的形式或集合多个目标文件的库文件形式来提供
函数,就可以不用公开标准函数的源代码内容

DLL文件及导入库 

  DLL(Dynamic Link Library动态链接库)文件:程序运行时动态结合的文件,存储了Windows API的目标文件

  API(Application Programming Interface应用程序接口):Windows 以函数的形式为应用提供了各种功能,这些形式的函数称为 API。例如,Sample1.c 中调用的MessageBox(),它并不是C语言的标准函数,而是Windows 提供的API 的一种。MessageBox() 提供了显示消息框的功能。

图8-8中import32.lib 中仅仅存储着两个信息,一是MessageBox() 在user32.dll 这个DLL 文件中,另一个是存储着DLL文件的文件夹信息,MessageBox() 的目标文件的实体实际上并不存在。我们把类似于import32.lib这样仅包含DLL文件中存储的函数信息的库文件称为导入库。

可执行文件的运行机制

 EXE文件是作为单独的文件储存在硬盘中的。通过资源管理器找到并双击EXE文件,就会把EXE 文件的内容加载到内存中运行。

变量和函数的内存地址

EXE文件作为本地代码的程序,并没有指定变量及函数的实际内存地址。是EXE文件中给变量及函数分配了虚拟的内存地址。在程序运行时,虚拟的内存地址会转换成实际的内存地址。链接器会在EXE文件的开头,追加转换内存地址所需的必要信息。这个信息称为再配置信息

EXE 文件的再配置信息,就成为了变量和函数的相对地址。相对地址表示的是相对于基点地址的偏移量,也就是相对距离。实现相对地址,也是需要花费一番心思的。在源代码中,虽然变量及函数是在不同位置分散记述的,但在链接后的EXE文件中,变量及函数就会变成一个连续排列的组。这样一来,各变量的内存地址就可以用相对于变量组起始位置这一基点的偏移量来表示,同样,各函数的内存地址也可以用相对于函数组起始位置这一基点的偏移量来表示。而各组基点的内存地址则是在程序运行时被分配的(图8-9)。

程序加载时生成栈和堆

  加载到内存的程序由四部分构成,见图8-10:

栈和堆需要的内存空间是在EXE文件加载到内存后开始运行时得到分配的。在高级编程语言中,编译器会自动生成指定栈和堆大小的代码,并将其附加到程序中。

:用来存储函数内部临时使用的变量(局部变量),以及函数调用时所用的参数的内存区域。

栈中对数据进行存储和舍弃(清理处理)的代码,是由编译器自动生成的,因此不需要程序员的参与。使用栈的数据的内存空间,每当函数被调用时都会得到申请分配,并在函数处理完毕后自动释放。

:用来存储程序运行时的任意数据及对象的内存区域。

堆的内存空间,则要根据程序员编写的程序,来明确进行申请分配或释放。C语言中是通过malloc() 函数来进行申请分配、通过free() 函数来释放的。而C++中则是通过new 运算符来申请分配、通过delete运算符来释放的。如果没有在程序中明确释放堆的内存空间,那么即使在处理完毕后,该内存空间仍会一直残留。这个现象称为内存泄露(memory leak),它是令C语言及C++的程序员们十分头疼的一个bug(程序的错误)。如果内存泄露一直存在的话,就有可能会造成内存不足而导致宕机。

结尾 

  本文总结了源文件到可执行文件的相关内容,介绍了由源文件到可执行文件的全过程,包括编译和链接,链接又分为静态链接和动态链接,需要理解源代码、目标代码、可执行的本地代码之间的相互关系以及链接过程中涉及到的库、导入库、DLL文件的相关概念。同时本章还简要介绍了可执行文件运行时的内存机制。

  这篇文章就总结到这里吧,下一篇可能重点总结程序从编写到运行的过程相关的内容。除此之外还会进一步更新红队打靶的解析和渗透测试相关的技术分享,恳请希望读者们多多支持。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/115986.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

计算机竞赛 深度学习 opencv python 实现中国交通标志识别

文章目录 0 前言1 yolov5实现中国交通标志检测2.算法原理2.1 算法简介2.2网络架构2.3 关键代码 3 数据集处理3.1 VOC格式介绍3.2 将中国交通标志检测数据集CCTSDB数据转换成VOC数据格式3.3 手动标注数据集 4 模型训练5 实现效果5.1 视频效果 6 最后 0 前言 &#x1f525; 优质…

YOLOv5、YOLOv8改进:空间金字塔池化 SPPF改为 SimSPPF / ASPP / RFB / SPPCSPC / SPPFCSPC

目录 1.SPP 2.sppf yolov5作者基于spp提出 3.YOLOv5/YOLOv8改进之结合​​ASPP 3.1空洞卷积&#xff08;Atrous Convolution&#xff09; 3.2空洞空间卷积池化金字塔 2.配置yolo.py文件 3.配置yolov5/yolov8_​​ASPP.yaml文件 4.1SimSPPF 5.RFB模块&#xff08;Recept…

搭建知识库系统不难,选对工具很重要!

建立科学的知识管理体系&#xff0c;增加企业的知识资产&#xff0c;对于企业本身来说是价值意义重大&#xff0c;但是很多企业受制于技术水平认知限制&#xff0c;总觉得搭建知识库系统非常难…… 以HelpLook为例&#xff0c;只需要注册登陆就可以在短时间内就可制作出一个简约…

MySQL 篇

目录 1、数据库三范式 2、数据库事务的特性 3、MySQL数据库引擎 4、说说 InnoDB 与 MyISAM 的区别 5、索引是什么&#xff1f; 6、索引数据结构 7、MySQL 索引类型有哪些&#xff1f; 8、索引有什么优缺点&#xff1f; 9、索引设计原则 9、使用索引应该注意些什…

LabVIEW崩溃问题解决方法

LabVIEW崩溃问题解决方法 LabVIEW在运行中出现崩溃的情况&#xff0c;确实让人很崩溃。不过按照下面的方法可以逐步排查解决。 在LabVIEW开发环境中浏览时&#xff0c;LabVIEW崩溃并显示以下错误&#xff1a; 解决方案 LabVIEW内部错误和崩溃的初步故障排除步骤&#xff1a;…

微信管理系统可以解决什么问题?

微信作为一款社交通讯软件&#xff0c;已经成为人们日常生活中不可缺少的工具。不仅个人&#xff0c;很多企业都用微信来联系客户、维护客户和营销&#xff0c;这自然而然就会有很多微信账号、手机也多&#xff0c;那管理起来就会带来很多的不便&#xff0c;而微信管理系统正好…

用PHP实现极验验证功能

极验验证是一种防机器人的验证机制&#xff0c;可以通过图像识别等方式来判断用户是否为真实用户。在实现极验验证功能时&#xff0c;您需要进行以下步骤&#xff1a; 1 注册极验账号&#xff1a; 首先&#xff0c;您需要在极验官网注册账号并创建一个应用&#xff0c;获取相应…

学习Shader中的光前了解光学简史

文章目录 前言一、光学的定义在这里插入图片描述 二、光的重要性和视觉息息相关 三、萌芽时期在这里插入图片描述 四、几何光学时期五、波动光学时期六、量子光学时期七 、现代光学时期 前言 引入光之前&#xff0c;我们只能使用Shader制作一些特效Shader、屏幕后处理Shader、…

sun.reflect.annotation.TypeNotPresentExceptionProxy

解决方法 找到TypeNotPresentExceptionProxy类,在sun.reflect.annotation下在TypeNotPresentExceptionProxy方法里打断点debug项目,查看断点错误 如图是缺少redis依赖导致的错误 https://www.cnblogs.com/qingmuchuanqi48/p/11716706.html

前端JavaScript入门到精通,javascript核心进阶ES6语法、API、js高级等基础知识和实战 —— JS基础(四)

开始吧&#xff0c;做时间的主人&#xff01; 把时间分给睡眠&#xff0c;分给书籍&#xff0c;分给运动&#xff0c; 分给花鸟树木和山川湖海&#xff0c; 分给你对这个世界的热爱&#xff0c; 而不是将自己浪费在无聊的人和事上。 思维导图 函数 为什么需要函数 <!DO…

在华为云服务器上CentOS 7安装单机版Redis

https://redis.io/是官网地址。 点击右上角的Download。 可以进入https://redis.io/download/——Redis官网下载最新版的网址。 然后在https://redis.io/download/页面往下拉&#xff0c;点击下图超链接这里。 进入https://download.redis.io/releases/下载自己需要的安装…

Java笔记:认识一下class文件

1.class文件概述 我们可任意打开一个Class文件&#xff08;使用Hex Editor等工具打开&#xff09;&#xff0c;内容如下&#xff08;内容是16进制&#xff09;&#xff1a; 十六进制转字符串&#xff1a;http://www.bejson.com/convert/ox2str/ 进制转换网址&#xff08;十六进…