
首先我们需要知道,不管是什么语言,我们编程写出来的是代码,最终写完后我们是需要让它运行起来。在Windows下,C语言写的代码最后是被转换成exe文件(或者dll),然后我们打开转换好的exe文件,就能让代码执行起来。
那么,我们写的一行一行的C语言代码,是如何变成可以双击运行的可执行代码的呢?
一、二进制0和1
这里首先普及一个常识,计算机其实并不聪明,因为它并不能直接读懂我们所写的任何代码,它能看懂的只有0和1。所有程序和软件,都是在最底层转换为计算机能读懂的1和0之后,才被执行起来的。那么,计算机能读懂0和1的由来是怎样的呢?举个简答的例子,我们可以把计算机中的晶体管想象成一个插线板,插线板上有8个插孔,插上线的孔表示1,没插上的就表示0,我们可以规定,只有第一个孔插上线的时候表示1,只有第二个孔插上线的时候表示2,第一个孔和第二个孔都插上线的时候表示1+2,也就是3,这样以此类推,一个插线板插线的情况就能表示很多情况。计算机中就是因为有这样上亿个类似“插线板”的晶体管,所以他能代表几百亿几千亿种情况。这就是为什么计算机虽然只能读懂0和1,但却又在表面上看起来那么智能,什么语音识别,人脸识别,智能AI,这些有的智能,归根结底都只是因为计算机内部的芯片集成度越来越高,处理速度越来越快。集成度高,就表示他能识别的情况越多,本来一台电脑只能运行10个程序,现在一台电脑能运行1000个程序;速度快,就表示以前我输入两个数计算要等10秒钟,现在我们输入一万个数,它1秒钟就能给我结果。一切都源于芯片的集成度高和处理速度快,电脑如此,平板、手机、智能音响都是如此。
既然计算机只能读懂0和1,那么我们写的C语言源代码计算机是怎么读懂的呢?这就涉及到我们任何代码想要运行,都要依靠的一个东西“编译器”和“解释器”。有的编程语言用“编译器”来转换为0和1,让计算机去运行,有的编程语言需要用“解释器”去让你写的代码执行起来。至于哪种编程语言用“编译器”哪种用“解释器”,这是编程语言最初在设计的时候就规定好的。
二、什么是编译器?
由源代码转换为能直接运行的可执行文件,这中间的转换过程非常复杂,但最重要最核心的中间过程有两个,一个是编译,一个是链接。简单的说,编译是把你写的源代码转换为机器可以读懂的0和1,链接就是把你源代码中需要依赖的其他东西给你包在一起。比如说,我写的代码是让张三下班后去接李四,经过编译成0和1后,计算机确实能读懂我的愿意,但计算机并不知道张三是谁,李四是谁啊,所以这就需要链接器在编译完成的时候,立刻就去检查你的代码中用到了哪些计算机找不着的东西,把它打包在一起,生成一个最终版。这个最终版计算机在运行后的时候,就不是执行“张三下班后去接李四”,他执行的可能是“这个人下班后去接那个人”,其中“这个人”和“那个人”是链接器已经打包好的,一下就能找到的人。(实例演示VS编程中哪些是编译错误,哪些是链接错误。)
三、什么是解释器?
解释器是另一类代码不是让计算机本身去运行,而是让其他程序把我的执行结果展现出来的东西。比如html,bat,python,这一类编程语言写好后,html是要让浏览器运行的,bat是要让cmd去执行的,python代码是要让你安装好的python程序执行的,他们都不是让计算机直接运行的,都是间接的通过一个中介,把自己要表达的意思展现出来而已。这个中介就叫做“解释器”。
所有的编程语言,或者属于“编译”型语言,或者属于“解释”型语言,或者两者都用到了,比如Java,Java既有编译型语言特征,又有解释型语言特征,两种方式一起用。通常情况下,我们要执行哪一种语言,就需要安装该语言的编译器或者解释器。但也存在一些特殊情况,比如html,我们在桌面新建一个index.html的文件,里面写上<h1>hello</h1>
,然后双击打开这个文件,我们会发现在你没有装任何浏览器的情况下,默认用IE浏览器打开了,页面上显示一个行黑体大字“hello”,而 index,html 里面的这个代码,就用了windows自带的IE浏览器作为解释器执行了起来。同样,你新建一个1.bat文件,里面写上"pause",然后双击运行,发现自动打开了命令行窗口,这便是默认使用了windows中自带的cmd命令行解释器执行了代码。
也就是说,所有代码执行要么必须安装第三方“编译器”或“解释器”来执行,要么就调用系统自带的工具去执行。可以确定的是,代码绝对不会平白无故的运行起来,肯定要有一个“执行”的“引导者”。
四、“编译”和“解释”的区别
知道了什么是“编译”型语言,什么是“解释”型语言后,那么问题来了,大家都是为了把代码执行起来,为什么弄出这两种方式出来呢?
前面我们讲了,在计算机中最底层的执行,都是执行的二进制01代码。“编译”型语言是代码在运行前,先编译成计算机直接能读懂的二进制代码,然后就能直接运行,而“解释”型语言是直接把代码一行一行的交给解释器去“解读”,解释一行,执行一行。一个是“你先等一会,我把所有代码都变成计算机能读懂的二进制,然后再全部给你”,另一个是“你现在不用等,给我一行,我给你解释一行,解释完了我把结果给展示出来,你再给我下一行”。
“编译”型语语言根据代码的多少,编译的过程需要等待一段时间,然后执行的时候就不需要再有等待动作。“解释”型语言不管代码的多少,只要你开始运行,我立马就能一行一行的给你执行,但每一行执行的过程中,都带有内部“解释”的动作。
所以答案来了,“编译”型语言主要用于底层数据处理,对处理效率要求较高,“等不起”的一些需求。比如杀毒软件,我不管你源代码生成杀毒软件的编译过程需要多久,因为这个过程不在我机器上进行,你最后给我的软件,我只在乎他查杀病毒的速度够不够快,我不想扫描个病毒让我等一个月。而“解释”型语言主要用于对时间要求没有那么“紧急”的需求上,比如一个页面的显示,假如这个页面很长有10屏,你需要不断滚动鼠标滑轮才能看到后面内容,那你需要在一开始就把10屏内容全部显示在页面上吗?即使全部显示,你也看不过来,还不如我先给你一屏内容你先看着,你看的过程中我在后面慢慢加载第二屏。而且还有很重要的一点,网页的代码根据用户需求,需要不断修改变动,如果100行代码我只修改了1行,我不需要等待100行代码重新编译后再运行,只需要拿出这一行变动的代码让解释器重新解释即可,这样效率岂不是更高。(实际处理远没这么简单,此处只做类比)
五、程序整体执行流程
拿我们要学的C语言举例:首先我们编写源代码,然后在“编译器”和“链接器”的复杂处理后,将源码转换为二进制的exe文件。当我们双击exe文件时,系统会在后台自动打开这个文件,先读取里面的内容,检查编译出来的exe格式是否合法,如果合法,就装载系统内存中,在装载的过程中,会把链接器指定的依赖文件一起加载。最后都加载完成后,就开始执行里面的功能。这个过程就是这样的。
平时我们打开软件遇到的最常见的两种错误,也可以用在这里得到解释了。
- 第一种是打开有些软件的时候,图示我们“缺少X动态链接库dll文件”,运行失败。这种错误就是程序在加载到内存阶段发生的,还并没有到执行内容的阶段。系统在加载链接器所指定的依赖文件时,如果找不到那个文件就会报这种错。
不是说链接器把所有依赖文件都“打包”好了吗?其实这里的“打包”有两种方式,一是“大包大揽”,所有的依赖库文件全部给融合到一个exe文件里,这一种不会出现上面说的错误,但这个exe的文件会很大,因为所有的依赖文件都在里面。第二种是“指点迷津”,我只把你需要的依赖文件给你明确的指出来在哪个位置,系统你按照我说的找,就能找到。这一种exe体积小,用起来更方便。而这一种出现上面问题的原因,往往是你移动了软件的位置,或者依赖库文件的位置。本来按照说好的位置找能找到,你后面改动了,当然就找不到了。如果不小心误删除,或者中了木马病毒,也有可能出现这种情况。
- 第二种是软件在运行了一段时间后,突然崩溃了。这种错误是加载过程已经完成,在执行内部代码的时候出现的错误。导致这种错误的原因,一般是你写程序的时候编写代码的错误。比如代码中你不小心写上了 1/0 的计算,就会有这种错误。
所有这里有一点误区,有的同学说别人给我发了一个木马过来,就在我桌面,你看我中病毒了!其实是并没有。任何文件,如果只是单纯存储在我们的电脑中,是没有任何威胁的。只有当我们打开它,运行成功的时候,才会中招。而且有的时候,即使你不小心运行了某个木马病毒,也不一定运行成功,因为这个东西是从别人那里传过来的,只有这个程序写的兼容性特别好,所以代码没有BUG,需要的链接库文件在你电脑也齐全,他才能正常执行。