深度挖掘.c到.exe的整个过程,透过现象看本质

news2024/11/19 7:33:06

文章目录

  • 程序的翻译环境和执行环境
  • 翻译环境
    • 编译
      • 预编译
        • 头文件的包含
        • 删除注释
        • 替换#define定义的符号
      • 编译
        • 词法分析
        • 语法分析
        • 语义分析
        • 符号汇总
      • 汇编
    • 链接
      • 合并段表
      • 符号表的合并和重定位
  • 执行环境

在这里插入图片描述

程序的翻译环境和执行环境

在ANSI C的任何一种实现中,存在两个不同的环境。

第1种是翻译环境,在这个环境中源代码被转换为可执行的机器指令。
从 .c 到.exe的过程中需要依赖翻译环境

第2种是执行环境,它用于实际执行代码

翻译环境

编译

编译过程其实又被细分为三个环节,即预编译,编译和汇编

组成一个程序的每个源文件(以.c为后缀的文件)通过编译过程分别转换成目标代码(也就是以.obj为后缀的文件 )

每个目标文件由链接器(linker)捆绑在一起,形成一个单一而完整的可执行程序(.exe)。

链接器同时也会引入标准C函数库中任何被该程序所用到的函数,而且它可以搜索程序员个人
的程序库,将其需要的函数也链接到程序中

如图:

在这里插入图片描述

总结:每个源文件 单独经过 编译器 处理,生成 目标文件 ,所有目标文件与链接库 一起,在 链接器的作用下生成可执行文件

先写一段代码 :

#include<stdio.h>

int main()
{
	printf("hehe\n");
	return 0;
}

运行后, 查看 .exe 文件 ,.exe 其实就是可执行程序

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

add.obj 和test.obj 这两个目标文件就是经过编译器的处理,最终生成了目标文件( .obj)
将test.c的文件预编译后生成test.i

预编译

预编译过程主要处理那些源代码文件中的以“#”开始的预编译指令。比如“#include”、“#define”等,主要处理规则如下:

  • 将所有的“#define”删除,并且展开所有的宏定义。

  • 处理所有条件预编译指令,比如“#if”、“#ifdef”、“#elif”、“#else”、“#endif”。

  • 处理“#include”预编译指令,将被包含的文件插入到该预编译指令的位置。注意,这个过程是递归进行的,也就是说被包含的文件可能还包含其他文件。

  • 删除所有的注释“//”和“/**/”。

  • 添加行号和文件名标识,比如#2“hello.c”2,以便于编译时编译器产生调试用的行号信息及用于编译时产生编译错误或警告时能够显示行号。

  • 保留所有的#pragma编译器指令,因为编译器须要使用它们。

经过预编译后的.i文件不包含任何宏定义,因为所有的宏已经被展开,并且包含的文件
也已经被插入到.i文件中。所以当我们无法判断宏定义是否正确或头文件包含是否正确时,可以查看预编译后的文件来确定问题。

在预编译这个环节,我们主要剖析三个部分 : 头文件的包含 、删除注释 、替换#define定义的符号

头文件的包含

在预编译阶段,编译器会将代码中所包含的头文件都替换成头文件的内容。例如,#include <stdio.h>这句代码会被替换成stdio.h这个头文件中的全部代码

test.c 文件里面有#include"test.h" ,#include"test.h" 的作用是将test.h的文件拷贝一份放到 test.i中

在这里插入图片描述

删除注释

在预编译阶段,编译器会将代码中所有的注释“//”和“/**/”

替换#define定义的符号

#define定义的标识符也好,定义的宏也罢,都是起到替换的作用,而真正进行替换的时刻便是预编译阶段

预编译阶段做的3件事,实际上都是一些文本操作,并没有运行该代码

编译

编译过程就是把预处理完的文件进行一系列词法分析、语法分析、语义分析及优化后生产相应的汇编代码文件,这个过程往往是我们所说的整个程序构建的核心部分,也是最复杂的部分之一。我们先简单介绍编译的具体几个步骤,这涉及编译原理等一些内容

在这里插入图片描述

词法分析

比如我们有一行C语言的源代码如下:

array[index] = (index+4)*(2+6)

首先源代码程序被输入到扫描器( Scanner),扫描器的任务很简单,它只是简单地进行词法分析,运用一种类似于有限状态机(Finite State Machine〉的算法可以很轻松地将源代码的字符序列分割成一系列的记号(Token)。
比如上面的那行程序,总共包含了28个非空字符,经过扫描以后,产生了16个记号,如表所示。

在这里插入图片描述

词法分析产生的记号一般可以分为如下几类:关键字、标识符、字面量(包含数字、字符串等)和特殊符号(如加号、等号)。在识别记号的同时,扫描器也完成了其他工作。比如将标识符存放到符号表,将数字、字符串常量存放到文字表等,以备后面的步骤使用。

有一个叫做lex的程序可以实现词法扫描,它会按照用户之前描述好的词法规则将输入的字符串分割成一个个记号。因为这样一个程序的存在,编译器的开发者就无须为每个编译器开发个独立的词法扫描器,而是根据需要改变词法规则就可以了

另外对于一些有预处理的语言,比如C语言,它的宏替换和文件包含等工作一般不归入编译器的范围而交给一个独立的预处理器。

语法分析

接下来语法分析器(Grammar Parser)将对由扫描器产生的记号进行语法分析,从而产生语法树(Syntax Tree)。
整个分析过程采用了上下文无关语法(Context-free Grammar)的分析手段,如果你对上下文无关语法及下推自动机很熟悉,那么应该很好理解。否则,可以参考一些计算理论的资料,一般都会有很详细的介绍。此处不再赘述。
简单地讲,由语法分析器生成的语法树就是以表达式(Expression)为节点的树
我们知道,C语言的一个语句是一个表达式,而复杂的语句是很多表达式的组合。
上面例子中的语句就是一个由赋值表达式、加法表达式、乘法表达式、数组表达式、括号表达式组成的复杂语句。它在经过语法分析器以后形成如图所示的语法树。

在这里插入图片描述

从图中我们可以看到,整个语句被看作是一个赋值表达式;
赋值表达式的左边是一个数组表达式,它的右边是一个乘法表达式;数组表达式又由两个符号表达式组成,等等。
符号和数字是最小的表达式,它们不是由其他的表达式来组成的,所以它们通常作为整个语法树的叶节点。
在语法分析的同时,很多运算符号的优先级和含义也被确定下来了。比如乘法表达式的优先级比加法高,而圆括号表达式的优先级比乘法高,等等。
另外有些符号具有多重含义,比如星号*在C语言中可以表示乘法表达式,也可以表示对指针取内容的表达式,所以语法分析阶段必须对这些内容进行区分。
如果出现了表达式不合法,比如各种括号不匹配、表达式中缺少操作符等,编译器就会报告语法分析阶段的错误

正如前面词法分析有lex一样,
语法分析也有一个现成的工具叫做yacc ( Yet AnotherCompiler Compiler )。它也像lex一样,可以根据用户给定的语法规则对输入的记号序列进行解析,从而构建出一棵语法树。
对于不同的编程语言,编译器的开发者只须改变语法规则,而无须为每个编译器编写一个语法分析器,所以它又被称为“编译器编译器(CompilerCompiler)”。

语义分析

接下来进行的是语义分析,由语义分析器(Semantic Analyzer)来完成。
语法分析仅仪是完成了对表达式的语法层面的分析,但是它并不了解这个语句是否真正有意义。

比如C语言里面两个指针做乘法运算是没有意义的,但是这个语句在语法上是合法的;比如同样一个指针和一个浮点数做乘法运算是否合法等。

编译器所能分析的语义是静态语义( StaticSemantic),所谓静态语义是指在编译期可以确定的语义,与之对应的动态语义(DynamicSemantic)就是只有在运行期才能确定的语义。

经过语义分析阶段以后,整个语法树的表达式都被标识了类型,
如果有些类型需要做隐式转换,语义分析程序会在语法树中插入相应的的转换节点。
上面描述的语法树在经过语义分析阶段以后成为如图所示的形式

在这里插入图片描述

符号汇总

在这个环节中,会将每个源文件的全局范围的变量符号进行汇总

在这里插入图片描述

汇编

汇编器是将汇编代码转变成机器可以执行的指令,每一个汇编语句几乎都对应一条机器指令。所以汇编器的汇编过程相对于编译器来讲比较简单,它没有复杂的语法,也没有语义,也不需要做指令优化,只是根据汇编指令和机器指令的对照表一一翻译就可以了

在这里插入图片描述

将汇编代码翻译成二级制指令 ,这个二进制指令存放在目标文件中 ,同时会给每个源文件汇总出来的符号分配一个地址,然后分别生成一个符号表 ,编译步骤里的符号汇总就是为汇编形成的符号表服务的

在这里插入图片描述

test.c文件中提取的符号Add只是Add函数的声明,并不是定义,无法判断Add函数是否真正存在,所以test.c生成符号表时分配给Add符号的地址是一个无意义(非法)的地址

链接

合并段表

(vs生成的目标文件的后缀是.obj , gcc生成的目标文件后缀是.o)

汇编结束后所生成的obj文件内部会被划分为几个段,在链接过程中就会把每个obj文件对应的段通过某种规则合并起来,最后形成可执行程序(.exe为后缀)

在这里插入图片描述

符号表的合并和重定位

程序并不是一写好就永远不变化的,它可能会经常被修改。

比如我们在第1条指令之后、第5条指令之前插入了一条或多条指令,那么第5条指令及后面的指令的位置将会相应地往后移动,原先第一条指令的低4位的数字将需要相应地调整。

在这个过程中,我们需要人工重新计算每个子程序或跳转的目标地址。当程序修改的时候,这些位置都要重新计算,十分繁琐又耗时,并且很容易出错。这种重新计算各个目标的地址过程被叫做重定位

在这里插入图片描述

符号表并非无意义。如果需要调用某一函数时,编译器会在符号表中查找该符号,如果有,则调用成功,否则调用失败
符号表有来自全局变量 、函数等 ,不是所有的符号符号表都有 ,局部的变量不会存在符号表,因为局部变量只能在局部范围内使用 ,是不能跨文件使用

在一个程序被分割成多个模块以后,这些模块之间最后如何组合形成一个单一的程序是须解决的问题。
模块之间如何组合的问题可以归结为模块之间如何通信的问题,最常见的属于静态语言的C/C++模块之间通信有两种方式,
一种是模块间的函数调用,另外一种是模块间的变量访问。
函数访问须知道目标函数的地址,变量访问也须知道目标变量的地址,所以这两种方式都可以归结为一种方式,那就是模块间符号的引用。
模块间依靠符号来通信类似于拼图版,定义符号的模块多出一块区域,引用该符号的模块刚好少了那一块区域,两者一拼接刚好完美组合,模块的拼接过程就是链接(Linking)。

执行环境

exe程序执行的过程大概可以分为四个步骤:

程序首先要载入内存中。 在有操作系统的环境中,该操作一般由操作系统来完成。在独立的环境中,程序的载入可以由手工完成,也可以通过可执行代码置入只读内存来完成。
程序的执行开始。 接着便调用main函数。
开始执行程序代码。 这个时候程序将使用一个运行时堆栈(stack),存储函数的局部变量和返回地址。程序同时也可以使用静态(static)内存,存储于静态内存中的变量在程序的整个执行过程一直保留它们的值。
终止程序。 正常终止main函数,也可能是意外终止。

如果你觉得这篇文章对你有帮助,不妨动动手指给点赞收藏加转发,给鄃鳕一个大大的关注
你们的每一次支持都将转化为我前进的动力!!!

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

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

相关文章

【Jenkins】使用java -jar jenkins.war --httpPort=XXXX启动Jenkins报错【解决方案】

使用java -jar jenkins.war --httpPortXXXX启动Jenkins报错【解决方案】 &#x1f449;欢迎关注博主【米码收割机】 &#x1f449;一起学习C、Python主流编程语言。 &#x1f449;机器人、人工智能等相关领域开发技术。 &#x1f449;主流开发、测试技能。 文章目录 使用java -…

学习分享|一文搞懂WiFi 6/7 以及选择路由器改造网络那些事

目录 什么是 WiFi 6 WiFi 6 功能特点 WIFI 6 与前几代对比 速度更快 延时更低 容量更大 更安全 更省电 WiFi 4~WiFi 6对比 WiFi 6 核心技术 WiFi 7 WiFi 世代列表 路由器常用技术扩展 2.5Ge 网口 WAN/LAN口复用/网口盲插 双WAN口 双LAN口端口聚合 mesh组网 聊…

实验四 文件系统原理与模拟实现

实验四 文件系统原理与模拟实现 代码资源地址 Java实现的混合索引和成组链接法算法资源-CSDN文库 实验目的&#xff1a; 了解操作系统中文件系统的结构和管理过程&#xff0c;掌握经典的算法&#xff1a;混合索引与成组链接法等方法。 实验内容&#xff1a; 编程模拟实现混合…

【Android取证篇】ADB版本更新详细步骤

【Android取证篇】ADB版本更新详细步骤 更新ADB版本&#xff0c;解决无法连接设备问题【蘇小沐】 ADB没有自动更新的命令&#xff0c;我们需要下载新的ADB进行替换更新。 1、ADB查找 打开任务管理器&#xff08;快捷键shiftctrlEsc或WinX&#xff09;&#xff0c;在“详细信…

Arcgis通过矢量建筑面找到POI对应的标准地址

背景 有时候我们需要找到POI对应的标准地址,也许有很多的方法, 比如通过POI的地址数据和标准地址做匹配,用sql语句就能实现; 但是POI数据中也存在很多没有地址数据的,这时候只能通过空间关联来匹配对应的标准地址了,而空间关联也有不一样的方法,一个是通过空间连接,找…

数智化转型再加速,低代码开发助力企业转型

毫无疑问&#xff0c;随着数智化转型的加速&#xff0c;越来越多的企业正在把数智化战略提升到一个全新的高度&#xff0c;转型的进程也正从“浅层次”的数智化走向“深层次”数智化的阶段。 据权威机构数据统计&#xff0c;过去几年全球数字经济同比增长15.6%&#xff0c;采取…

DJ5-5/6 与设备无关的 I/O 软件、用户层的 I/O 软件

目录 5.5 与设备无关的 I/O 软件 5.5.1 与设备无关软件的概念 5.5.2 与设备无关的软件的功能 5.5.3 设备分配 5.5.4 逻辑设备名到物理设备名映射的实现 5.6 用户层的 I/O 软件 5.6.1 系统调用与库函数 5.6.2 假脱机技术 SPOOLing 5.5 与设备无关的 I/O 软件 …

鲲鹏昇腾开发者峰会2023举办

[2023年5月6日 广东东莞]今天&#xff0c;以“创未来 享非凡”为主题的鲲鹏昇腾开发者峰会2023在东莞松山湖举办。 鲲鹏产业生态繁荣&#xff0c;稳步发展&#xff0c;正在成为行业核心场景及科研领域首选&#xff0c;加速推动数字化转型&#xff1b;昇腾产业快速蓬勃向上&…

【大数据之Hadoop】二十五、生产调优-HDFS核心参数

1 NameNode内存生产配置 Hadoop3.x系列的NameNode内存是动态分配的&#xff0c;可以用jmap -heap 进程号 查看分配的内存。 在hadoop102中NameNode和DataNode的内存都是自动分配的&#xff0c;且相等。 根据经验&#xff1a; NameNode最小值为1G&#xff0c;每增加1百万个物理…

【JavaEE初阶】多线程带来的风险~线程安全

目录 &#x1f31f;观察线程不安全的现象 &#x1f31f;线程不安全的原因 &#x1f308;1、多个线程修改了同一个共享变量 &#x1f308;2、线程是抢占式执行的&#xff0c;CPU的调度是随机的 &#x1f308;3、指令执行时没有保证原子性 &#x1f308;4、多线程环境中内…

当无触控板和鼠标的情况下,如何开启触控板

背景&#xff1a;一次出行匆忙&#xff0c;忘记带鼠标&#xff0c;周围也无可用工具&#xff0c;主要是触控板当时也被我关闭了&#xff0c;下面讲述一下我是如何解决在没有鼠标的情况下开启触控板的。 首先我们开启电脑后&#xff0c; 存在两种思路去开启触控板 第一种方案…

加拿大访问学者签证材料清单

加拿大在教育、政府透明度、社会自由度以及生活品质等方面在国际上排名名列前茅&#xff0c;出于环境、社会氛围等因素&#xff0c;不少学者将目光聚焦于这个北美的发达国家。加拿大的访问学者签证属于工作签证&#xff0c;过去只要有邀请函就可以办理&#xff0c;但是自去年2月…

Python:Python底层原理:Python的整数是如何实现的

Python整数在底层存储方式 1. Python整数在底层对应的结构体 PyLongObject2.整数是怎么存储的2.1 整数0存储2.2 整数12.3 整数-12.4. 2**30 -12.5 . 2**302.6 . ob_digit[a, b, c] 对应整数计算 计算整数所占内存大小总结 Python的底层是C/C &#xff0c;但是 C/C 能表示的整数…

Linux挂载新磁盘到根目录

添加磁盘到需要挂载的机器上 lsblk查看硬盘挂载情况&#xff0c;sdb,sdc为我新挂载的磁盘 fdisk -l查看挂载之前的分区情况 为新硬盘创建分区 fdisk /dev/sdb 终端会提示&#xff1a; Command &#xff08;m for help&#xff09;&#xff1a;输入&#xff1a;n 依次输入p…

【HTTPS】

HTTP明文传输问题 窃听风险&#xff0c;比如通信链路上可以获取通信内容&#xff0c;用户号容易没。篡改风险&#xff0c;比如强制植入垃圾广告&#xff0c;视觉污染&#xff0c;用户眼容易瞎。冒充风险&#xff0c;比如冒充淘宝网站&#xff0c;用户钱容易没。 TLS协议解决H…

【雅可比左乘右乘】

常见雅可比左乘&#xff08;以自变量R为例子&#xff0c;围绕旋转点p的旋转点的左扰动雅可比&#xff09;&#xff1a; 旋转点的右扰动雅可比&#xff08;右乘&#xff09;&#xff1a; 左雅可比和右雅可比之间的区别在于它们各自描述了不同的变换方向。左雅可比将输入空…

硬件-6-基站和移动通信系统的演进

1G、2G、3G、4G、5G 移动通信技术发展简史 1 移动通信系统简介 移动通信系统从第一代移动通信系统(1G)开始逐渐发展&#xff0c;目前已经发展到第四代移动通信系统(4G)&#xff0c;第五代移动通信系统(5G)也已经开始标准化&#xff0c;预计2020年商用&#xff0c;6G预计2030年…

Linux网络架构: XDP, iptables/netfilter和iproute2/tc/ip/Qdiscs

本文目录 1、架构框图2、网络架构分成三大块3、网络架构-----对应的配置工具-----对应的原理与概念 说到Linux的网络架构&#xff0c;就离不开谈。。。这些东西。这几个概念很容易混淆起来&#xff0c;但如果仔细去看&#xff0c;就会发现这个Linux的网络架构的设计其实是非常简…

10:00面试,10:04就出来了 ,问的实在是太...

从外包出来&#xff0c;没想到竟然死在了另一家厂子 自从加入这家公司&#xff0c;每天都在加班&#xff0c;钱倒是给的不少&#xff0c;所以我也就忍了。没想到12月一纸通知&#xff0c;所有人都不许加班&#xff0c;薪资直降30%&#xff0c;顿时有吃不起饭的赶脚。 好在有个…

建筑专业可以转行学云计算吗?

当然可行。 在过去的几年中&#xff0c;我们已经帮助很多建筑土木工程专业的同学转行学习云计算技术&#xff0c;尤其是在建筑信息化编程方向。近年来&#xff0c;云计算行业持续发展&#xff0c;涉及到众多领域&#xff0c;如云数据中心、云安全、云存储、云计算机服务等。云…