一个程序从编译到运行的全过程

一个程序从编译到运行的全过程

  • 一个程序从编译到运行的全过程
    • 编译
      • 预处理
      • 编译
    • 汇编
    • 链接
    • 载入
      • 虚拟内存
      • 用户空间
    • 总结

一个程序从编译到运行的全过程

每次用编译器写完一个程序后,我们会进行调试和执行,将代码的结果输出在我们的电脑屏幕上,但是我们并不清楚,为什么我们写的这些代码,可以转为我们想要的结果呢?

其实,在ANSI C(标准C)的任何一种实现中,都存在两个不同的环境,即翻译环境执行环境

翻译环境下,程序会经过两个阶段,一个是编译,一个是链接。翻译环境内,代码被转换为可执行的机器指令。执行环境内,它用于实际执行的代码。

那么,我们的程序就是进行了这两个过程,才最终将我们想要的结果输出在屏幕上。

一个程序,从编写完代码,到被计算机运行,总共需要经历以下四步:

  1. 编译:编译器会将程序源代码编译成汇编代码。
  2. 汇编:汇编器会将汇编代码文件翻译成为二进制的机器码。
  3. 链接:链接器会将一个个目标文件和库文件链接在一起,成为一个完整的可执行程序。
  4. 载入:加载器会将可执行文件的代码和数据从硬盘加载到内存中,然后跳转到程序的第一条指令处开始运行。

链接器和加载器是由操作系统实现的程序。而编译器和汇编器则是由不同的编程语言自己实现的了。

这里需要展开来说一说,我们常用的高级语言,按照转化成机器码的方式不同可以分为编译型语言解释型语言

  1. 编译型语言要求由编译器提前将源代码一次性转换成二进制指令,即生成一个可执行程序,后续的执行无需重新编译。比如我们常见的 C、Golang 等,优点是执行效率高;缺点是可执行程序不能跨平台(不同的操作系统对不同的可执行文件的内部结构要求不同;另外,由于不同操作系统支持的函数等也可能不同,所以部分源代码也不能跨平台)。
  2. 解释型语言不需要提前编译,程序只在运行时才由解释器翻译成机器码,每执行依次就要翻译一次。比如我们常见的 Python、PHP 等,优点是较方便(对编写用户而言,省去了编译的步骤),实时性高(每次修改代码后都可直接运行),能跨平台;缺点是效率低。
  3. 半编译半解释型语言:还有一类比较特殊,混合了两种方式。源代码需要先编译成一种中间文件(字节码文件),然后再将中间文件拿到虚拟机中解释执行。比如我们常见的 Java、C# 等。

所以,要设计一门语言,还必须为其编写相应的编译器和解释器,将源代码转化为计算机可执行的机器码。由于不同的语言有不同的转化方式,接下来将以最常见的 C 语言为例,简单分析一下 编译→汇编→链接→载入 的过程。

总结:不同的语言会使用不同的方式将源代码转化为机器码,但是之后的链接和载入过程都是由操作系统完成的,都是相同的。

编译

编译是读取源程序,进行词法和语法分析,将高级语言代码转换为汇编代码。整个编译过程可以分为两个阶段。

预处理

  1. 对其中的伪指令(以 # 开头的指令)进行处理。
    • #define 定义的符号替换。我们通常使用define定义标识符和宏。假设,我们这里顶一个一个MAX为100,#define MAX 100 ,那么在预编译阶段,我们程序中的MAX会被直接替换成100。;
    • 处理条件编译指令。如 #if、#elif、#else、endif 等;
    • 处理头文件包含指令。如 #include,将被包含的文件插入到该预编译指令的位置;
  2. 删除所有的注释。
  3. 添加行号和文件名标识。

编译

对预处理完的文件进行一系列词法分析、语法分析、语义分析及优化后,产生相应的汇编代码文件。

过程比较复杂,主要包括四个步骤:

  1. 词法分析。这一过程是将字符序列转为单词序列。这里的单词是一个字符串,是构成源代码的最小单位。从输入字符流中生成单词的过程叫作单词化,同时会对单词进行分类。比如:sum = 2+3; 将会拆成sum、=、2、+、3 这些单词。
  2. 语法分析。这一过程是分析单词符号串是否形成符合语法规则的语法单位。如表达式、赋值、循环等,最终是否构成一个符合要求的程序,再按照对应语言的语法规则检查每条语句是否正确。
  3. 语义分析。语义分析是对结构上正确的源程序进行上下文有关性质的审查,进行类型审查。比如语义分析的一个工作是进行类型审查,审查每个运算符是否具有语言规范允许的运算对象,当不符合语言规范时,编译程序 应报告错误。
  4. 符号汇总。这个环节会将源文件的全局函数的函数名进行汇总。

汇编

将编译完的汇编代码文件翻译成机器指令,保存在后缀为 .o 的目标文件(Object File)中。同时会给每个源文件汇总出来的符号分配一个地址,然后生成一个符号表。

这个文件是一个 ELF 格式的文件(Executable and Linkable Format,可执行可链接文件格式),包括可以被执行的文件和可以被链接的文件(如目标文件 .o,可执行文件 .exe,共享目标文件 .so),有其固定的格式。

链接

由汇编程序生成的目标文件并不能被立即执行,还需要通过链接器(Linker),将有关的目标文件彼此相连接,使得所有的目标文件成为一个能够被操作系统载入执行的统一整体。

例如在某个源文件的函数中调用了另一个源文件中的函数;或者调用了库文件中的函数等等情况,都需要经过链接才能使用。

在链接阶段,编译器主要完成两个任务:

  1. 合并段表。在汇编结束生成的obj文件,内部其实会被划分为几个段,在链接过程中会将这些段进行合并。
  2. 符合表的合并和重定位。在链接过程中,我们会对不同的符合分配一个相应的地址。而有的时候,一些符号在它所处的文件中并不存在,存在于另一个文件中,这时候我们会提前分配一个无意义的地址。在合并之后,会将合法的地址重新定位覆盖原来无意义的地址。

链接处理可以分为两种:

  • 静态链接:直接在编译阶段就把静态库加入到可执行文件当中去。优点:不用担心目标用户缺少库文件。缺点:最终的可执行文件会较大;且多个应用程序之间无法共享库文件,会造成内存浪费。
  • 动态链接:在链接阶段只加入一些描述信息,等到程序执行时再从系统中把相应的动态库加载到内存中去。优点:可执行文件小;多个应用程序之间可以共享库文件。缺点:需要保证目标用户有相应的库文件。

载入

加载器(Loader)会将可执行文件的代码和数据加载到内存(虚拟内存)中,然后跳转到程序的第一条指令开始执行程序。

虚拟内存

首先,为了避免进程所使用的内存地址相互影响,操作系统会为每个进程分配一套独立的虚拟内存地址,然后再提供一种机制,将虚拟内存地址和物理内存地址进行映射。

我们程序所使用的内存地址叫做虚拟内存地址(Virtual Memory Address),实际存在硬件里面的空间地址叫物理内存地址(Physical Memory Address)。

用户空间

然后,操作系统将整个内存空间分为用户空间和内核空间,其中内核空间只有内核程序能够访问,且所有进程共用一个内核空间;而用户空间是专门给应用程序使用的,每当创建了一个新的进程,都要分配一个用户空间。

接下来以 32 位内存空间为例进行说明,32 位内存空间大小为 4GB,其中 1GB 为内核空间,3GB 为用户空间。用户空间中按照数据类型不同,划分为了不同的内存段,各类数据会被存放到各自的内存段中。

在这里插入图片描述

用户空间内存,从低到高分别是 6 种不同的内存段:

  1. 程序文件段(.text),包括二进制可执行代码;
  2. 已初始化数据段(.data),包括静态常量;
  3. 未初始化数据段(.bss),包括未初始化的静态变量;
  4. 堆段,包括动态分配的内存,从低地址开始向上增长。当进程调用malloc等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张);当利用free等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减);
  5. 文件映射段,包括动态库、共享内存等,从低地址开始向上增长;
  6. 栈段,包括局部变量和函数调用的上下文等。栈的大小是固定的,一般是 8 MB。当然系统也提供了参数,以便我们自定义大小;栈段可以通过系统调用自动地扩充空间,但是不能回收空间,所以栈段设置得太大会导致内存泄露。

至此,可执行文件载入内存的过程可以概括为以下几步:

  1. 给进程分配虚拟内存空间;
  2. 创建虚拟地址到物理地址的映射,创建页表;
  3. 加载代码段和数据段等数据,即将硬盘中的文件拷贝到物理内存页中,并在页表中写入映射关系;
  4. 把可执行文件的入口地址写入到 CPU 的 指令寄存器(PC)中,即可执行程序。

总结

本文简单的描述了一下一个程序,从编写完代码,到被计算机运行的过程,其实当中的每一步都十分复杂深奥,都值得深入学习,尤其是最后一步载入内存的过程,展开来说可以涉及到整个操作系统的内存管理,像分段、分页、多级页表、TLB、内存分配、内存泄露、内存回收、页面置换算法等等。

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

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

相关文章

Python爬虫学习完整版

一、什么是爬虫 网络爬虫,是一种按照一定规则,自动抓取互联网信息的程序或者脚本。由于互联网数据的多样性和资源的有限性,根据用户需求定向抓取相关网页并分析也成为如今主流的爬取策略。 1 爬虫可以做什么 你可以爬取网络上的的图片&#…

全民采矿石赚钱小程序源码,附带详细搭建教程

安装教程 1、环境用宝塔Nginxphp7.0或者以下版本 2、可以更换各种模板,懂代码和标签的可以改模板,不懂的可以直接上站 3、上站前记得添加关键词和内容库 4、伪静态在绑定完百度站长之后再添加 目录说明: data/keyword 放关键词 标签&#xff…

OpenLayers基础教程——WebGLPoints图层样式的设置方法

1、前言 前一篇博客介绍了如何在OpenLayers中使用WebGLPoints加载海量数据点的方法,这篇博客就来介绍一下WebGLPoints图层的样式设置问题。 2、样式运算符 在VectorLayer图层中,我们只需要创建一个ol.style.Style对象即可,WebGLPoints则不…

浅谈Webmail邮件还原

Webmail还原,其实也就是HTTP协议的还原,而HTTP协议的还原,核心部分是TCP会话的重组。在TCP会话进行重组之后,再对重组的报文进行HTTP解析,得到Webmail中相应的信息。 由于每个邮件服务商实现Webmail的方式都各不相同&a…

LabVIEW智能降噪系统

LabVIEW智能降噪系统 随着噪声污染问题的日益严重,寻找有效的降噪技术变得尤为关键。介绍了一种基于LabVIEW平台开发的智能降噪系统,该系统能够实时采集环境噪声,并通过先进的信号处理技术实现主动降噪,从而有效改善生活和工作环…

CV论文--2024.3.26

1、DiffusionMTL: Learning Multi-Task Denoising Diffusion Model from Partially Annotated Data 中文标题:DiffusionMTL:从部分注释的数据中学习多任务去噪扩散模型 简介:最近,人们对于从部分标注数据中学习多个密集场景理解任…

qt table 简易封装,样式美化,以及 合并表格和颜色的区分 已解决

在需求中&#xff0c; 难免会使用 table 进行渲染窗口&#xff0c;做一个简单的封装。美化表格最终效果&#xff01;&#xff01;&#xff01; 代码部分 // 显示 20行 20列CCendDetailsInfoTableWidget* table new CCendDetailsInfoTableWidget(20,10);for (int i 0; i < …

蓝桥杯2023年第十四届省赛真题-买瓜|DFS+剪枝

题目链接&#xff1a; 0买瓜 - 蓝桥云课 (lanqiao.cn) 蓝桥杯2023年第十四届省赛真题-买瓜 - C语言网 (dotcpp.com) &#xff08;蓝桥官网的数据要求会高一些&#xff09; 说明&#xff1a; 这道题可以分析出&#xff1a;对一个瓜有三种选择&#xff1a; 不拿&#xff0c…

Hbase解决ERROR: KeeperErrorCode = ConnectionLoss for /hbase/master报错

在使用hbase时出错&#xff0c;错误如下图&#xff1a; 错误原因&#xff1a; 返回去检查启动的Hadoop与zookeeper&#xff0c;发现zookeeper的状态不对&#xff0c;重新启动了一下zookeeper&#xff0c;确保所有机器的zookeeper都启动起来了就可以了。

微服务(基础篇-004-Feign)

目录 http客户端Feign Feign替代RestTemplate&#xff08;1&#xff09; Feign的介绍&#xff08;1.1&#xff09; 使用Feign的步骤&#xff08;1.2&#xff09; 自定义配置&#xff08;2&#xff09; 配置Feign日志的两种方式&#xff08;2.1&#xff09; Feign使用优化…

【C++】哈希应用之位图

&#x1f440;樊梓慕&#xff1a;个人主页 &#x1f3a5;个人专栏&#xff1a;《C语言》《数据结构》《蓝桥杯试题》《LeetCode刷题笔记》《实训项目》《C》《Linux》《算法》 &#x1f31d;每一个不曾起舞的日子&#xff0c;都是对生命的辜负 目录 前言 1.位图的概念 2.位…

解决“Pycharm中Matplotlib图像不弹出独立的显示窗口”问题

matplotlib的绘图的结果默认显示在SciView窗口中, 而不是弹出独立的窗口, 这样看起来就不是很舒服&#xff0c;不习惯。 通过修改设置&#xff0c;改成独立弹出的窗口。 File—>Settings—>Tools—>Python Scientific—>Show plots in toolwindow 将√去掉即可

初识C++(三)构造函数和析构函数

目录 一、构造函数&#xff1a; 1.构造函数的概念&#xff1a; 2.构造函数的特性&#xff1a; 3.构造函数的形式&#xff1a; 4.为什么要引出构造函数这一概念 5.默认构造函数包括&#xff1a; 6.对默认生成的构造函数不处理内置类型的成员这事的解决办法&#xff1a; …

【Python机器学习系列】skearn机器学习模型的保存---pickle法

这是我的第246篇原创文章。 一、引言 pickle是Python 的标准库&#xff0c;用于序列化对象。可以使用 pickle.dump()将模型保存到文件&#xff0c;然后使用 pickle.load()从文件中加载模型。 序列化&#xff1a;指将一个对象转换为字节流&#xff0c;能够存储在文件或网络上&…

计算机网络:现代通信的基石

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

异地两台电脑如何共享文件?

在当前数字化时代&#xff0c;人们对于数据的使用和管理变得越来越便捷。由于工作和生活的需要&#xff0c;我们常常需要在异地的电脑间共享文件。这给我们的工作和生活带来了一定程度的不便。有没有一种便捷的方法可以让异地的电脑实现文件的共享呢&#xff1f;答案是肯定的。…

知识图谱-图数据库-neo4j (1)踩坑记录

1、neo4j 安装 材料 &#xff1a; openjdk11 (neo4j 最低jdk版本要求) neo4j-community-4.4.30 CentOS 7.8 Release Date: 25 January 2024 Neo4j 4.4.30 is a maintenance release with many important improvements and fixes. Neo4j Deployment Center - Graph Database…

【前端面试3+1】01闭包、跨域

一、对闭包的理解 定义&#xff1a; 闭包是指在一个函数内部定义的函数&#xff0c;并且该内部函数可以访问外部函数的变量。闭包使得函数内部的变量在函数执行完后仍然可以被访问和操作。 特点&#xff1a; 闭包可以访问外部函数的变量&#xff0c;即使外部函数已经执行完毕。…

Linux 搭建jenkins docker

jekin docker gitee docker 安装 jenkins docker run -d --restartalways \ --name jenkins -uroot -p 10340:8080 \ -p 10341:50000 \ -v /home/docker/jenkins:/var/jenkins_home \ -v /var/run/docker.sock:/var/run/docker.sock \ -v /usr/bin/docker:/usr/bin/docker je…

QT数据类型和容器用法

Qt库提供了基于通用模板的容器类, 这些类可用于存储指定类型的数据项&#xff0c;Qt中这些容器类的设计比STL容器更轻&#xff0c;更安全且更易于使用。容器类也都是隐式共的&#xff0c;它们是可重入的&#xff0c;并且已针对速度/低内存消耗和最小的内联代码扩展进行了优化&a…