(转载)你好,C++(4)2.1.3 个人父亲母亲:编译器和连接器 2.1.4 C++程序执行背后的故事

你好,C++(4)2.1.3 个人父亲母亲:编译器和连接器 2.1.4 C++程序执行背后的故事

 

2.1.3  个人父亲母亲:编译器和连接器

从表面上看,我是由Visual Studio建立的,而实际上,真正负责编译源代码建立生成可执行程序HelloWorld.exe的倒是Visual Studio中集成的C++编译器cl.exe和连接器link.exe。他们二老,才是个人亲生爹妈。html

为了便于人们的编写、阅读和维护,咱们的源文件是使用C++这种人们能够理解的高级程序设计语言编写的。然而,计算机却并不理解这种高级语言,也就没法直接执行高级语言编写而成的源文件。因此,这里就须要一个翻译的工做,将源文件中人们能够理解的C++高级语言翻译成机器能够理解执行的机器语言。我老爸编译器其实是个翻译官,他的工做就是将用C++这种高级语言编写的源文件(.cpp)翻译成用计算机能够看懂的机器语言表示的目标文件(.obj),你们一般将这一过程称为编译。ios

在Visual Studio中,我老爸的名字是cl.exe,你们能够在开始菜单中找到“VS2012 开发人员命令提示”,而后在打开的DOS窗口中经过cl命令请他老人家出手,将一个cpp源文件编译成相应的obj目标文件。好比,要想让我爸将个人源文件HelloWorld.cpp编译成对应的目标文件HelloWorld.obj,可使用下面的命令:程序员

cl /c /EHsc HelloWorld.cpp算法

其中,cl是调用编译器的指令,其后的选项用于指定编译器的编译行为。这里的“/c”表示只编译不连接;“/EHsc”指定编译器使用何种异常处理模型;最后一个选项HelloWorld.cpp则是即将要编译的C++源文件。源文件HelloWorld.cpp通过我爸编译器的编译后,获得的还只是一个没法直接执行的目标文件HelloWorld.obj,还须要我妈连接器将这个目标文件和Visual C++所提供的标准库目标文件(好比,libcpmt.lib)整合成最终的可执行文件(从标准库目标文件中查找程序目标文件所用到的外部函数等符号,而后填写到程序目标文件以生成最终的可执行文件),这一过程就被称之为连接。在“VS2012 开发人员命令提示”中,你们能够用以下的命令请我妈连接器link.exe来完成这一连接过程:数据结构

link HelloWorld.obj多线程

固然,整个编译连接的工做,也能够由我爸编译器cl.exe一我的完成:函数

cl /EHsc HelloWorld.cpppost

通过我爸我妈的编译连接过程,我从一个源文件(HelloWorld.cpp)变成了一个可执行文件(HelloWorld.exe),我就这样哇哇坠地了。整个过程,如图2-6所示:this

                       

图2-6 编译连接过程url

2.1.4  C++程序的执行过程

一旦生成可执行文件,就能够给操做系统下达指令让文件开始执行。一个程序的执行是从其主函数开始的。可是在进入主函数开始执行以前,操做系统会帮咱们作不少准备工做。好比,当操做系统接到执行某个程序的指令后,它首先要建立相应的进程并分配私有的进程空间;而后加载器会把可执行文件的数据段和代码段映射到进程的虚拟内存空间中;操做系统接着会初始化程序中定义的全局变量等。作好这些准备工做,程序就能够进入主函数开始执行了。

进入主函数后,程序会按照源代码给我制定的人生规划,一条语句一条语句地往下执行,一步一步地往下走。你们必定还记得,个人源代码是这样的:

复制代码
int main() { // 在屏幕上输出“Hello World!”字符串 cout<<"Hello World!"<<endl; return 0; }
复制代码

从这里能够看到,进入主函数后,个人第一条语句就是:

cout<<"Hello World!"<<endl;

这条语句的意思是让我在DOS窗口中显示“Hello World!”这样一串文字,因而我便开始控制DOS窗口,在其中显示这串文字,完成程序员经过这行代码交给个人任务。

接下来的一条语句是:

return 0;

这条简短的语句宣告了我人生历程的结束。它表示主函数的结束,整个程序执行完毕。图2-7所示的是我短暂而光辉的一辈子!

 

 

图2-7  Hello World程序短暂而光辉的一辈子

知道更多:C++程序执行背后的故事

在上面的例子中,咱们看到一个C++程序的执行过程,是从main()函数开始逐条语句往下执行的。这个过程看起来很是简单,但在每条语句的背后,都还有着更多的故事。

在Visual Studio调试模式下的反汇编视图(在调试模式下经过Alt+8快捷键打开)中,咱们能够看到C++程序中的各条语句所对应的汇编代码。这下,程序中各条语句作了什么事情、各个功能是如何实现的,都一目了然了。HelloWorld程序虽然只是简单地输出一个字符串,可是当咱们把这个程序拆解开,却能够发现它背后作了不少事情。在汇编视图下的HelloWorld程序以下(汇编代码太长,咱们只保留其中的关键操做):

复制代码
#include <iostream>

using namespace std; int main() { // 完成准备工做 00DC4EC0 push ebp 00DC4EC1 mov ebp,esp 00DC4EC3 sub esp,0C0h //00DC4EDC rep stos dword ptr es:[edi] // 完成任务 // 在屏幕输出“Hello World!”字符串 cout<<"Hello World!"<<endl; 00DC4EDE mov esi,esp 00DC4EE0 mov eax,dword ptr ds:[00DD031Ch] 00DC4EE5 push eax 00DC4EE6 push 0DCCC70h 00DC4EEB mov ecx,dword ptr ds:[0DD0318h] 00DC4EF1 push ecx // 调用标准库中的操做符来完成任务 00DC4EF2 call std::operator<<<std::char_traits<char> > (0DC12A3h) 00DC4EF7 add esp,8 00DC4EFA mov ecx,eax 00DC4EFC call dword ptr ds:[0DD0324h] 00DC4F02 cmp esi,esp 00DC4F04 call __RTC_CheckEsp (0DC132Ah) return 0; 00DC4F09 xor eax,eax }
复制代码

当咱们启动一个程序后,操做系统会建立一个新的进程来执行这个程序。所谓进程,就是应用程序的一个实例。操做系统建立进程的时候,会为其分配必定的内存空间(默认堆),做为其私有的虚拟地址空间。一般,一个应用程序的执行对应于一个进程,进程负责管理这个程序运行时的一切事物,例如资源的分配与调度等等。可是,做为程序执行的调度者,它并不负责程序的执行,具体的执行工做,则是由它所建立的线程来完成的。每一个进程都有一个主线程,若是是多线程应用程序,还能够有多个辅助线程。线程并不拥有资源(它使用的是它所属进程的资源),可是它拥有本身的执行入口、执行的顺序系列和一个执行终点。

在这里,当负责执行这个程序的主线程被建立之后,它就会进入main()函数开始执行。它首先会执行一些初始化工做,例如保存现场环境、对堆进行初始化以及完成程序参数的传递等等,而后才是执行具体的程序代码。虽然C++程序代码只有一行,可是在汇编视图下,却被分解成了多个步骤来完成。主函数的执行,也不过是对于一些寄存器的操做和对库函数的调用而已。例如,在main()函数的第一句就是用“push ebp”保存当前地址(在汇编代码中,ebp表明了当前地址)。这里咱们必定会感到奇怪,为何在进入main()函数后的第一件事不是在C++程序代码中看到的输出一个字符串,而是保存当前地址呢?实际上,咱们从程序代码中所看到的只是咱们对于要实现的功能的描述,而真正地要实现这些功能,C++程序还要在背后为咱们完成不少事情。这里的“push ebp”保存当前地址,就是为了让这个main()函数在执行完毕后,能够顺利返回原来的地址继续往下执行。除了对于寄存器的操做(push、mov以及pop等汇编指令)以外,汇编代码中更重要的是经过“call”指令完成的对其余函数的调用。例如,“call  __RTC_CheckEsp (0DC132Ah)”这个call指令就是调用__RTC_CheckEsp()函数(由编译器在调试版本中添加)在程序执行完毕后检查堆栈是否平衡。

在汇编视图下,咱们能够看到每一条C++语句后面都有故事。只有了解了每一条语句背后的故事,才能真正地理解这一条语句。这一样也告诉咱们,若是咱们发现某条语句的行为出现了异常而咱们又没法从代码层面找到缘由,咱们就须要从这条语句的背后寻找真正的缘由。

2.1.5  程序的两大任务:描述数据与处理数据

人们编写程序的目的,是为了用程序解决现实世界中的问题。人们观察发现,全部这些问题都是以数据做为输入,而后对这些数据进行处理,最后得到结果数据而使问题获得解决的。因此,既然我是用来帮助人们解决问题的,那么个人任务天然也就离不开对数据的描述和对数据的处理。如图2-8所示。

人们用公式给我下了一个定义: 

数据 + 算法 = 程序

其中,数据能够当作是对现实世界中的各个事物的抽象和描述。例如,在C++程序中,咱们将现实世界中的各类数据抽象成各类数据类型,好比咱们将整数抽象成int类幸,将小数抽象成double类型等。而后反过来用这些类型定义的变量来描述咱们在生活中遇到的某个具体的数据。好比,用int类型定义的变量nWidth来描述某个矩形的宽度;用string类型定义的变量strName来描述某我的的名字;咱们甚至还能够建立自定义的数据类型来描述更加复杂的事物,好比咱们能够建立一个Human数据类型来抽象“人”这个复琐事物,而后用它定义一个变量来描述某个具体的人。总之,用数据对现实世界中的事物进行抽象和描述,是个人第一个任务。

图2-8  个人人生目的

用数据对现实世界进行描述并非个人最终目的,个人最终目的是对这些数据进行处理,从而得到想要的结果数据。好比,咱们用nWidth和nHeight描述了一个矩形的宽和高,然而,这并非咱们想要的结果数据,咱们想要的是矩形的面积。因此,咱们还必须对nWidth和nHeight这两个数据进行处理,用“*”符号计算两个数的乘积,才能得到咱们想要的矩形面积。对数据处理过程的抽象,人们称之为算法。而个人第二个任务,就是描述和表达算法,对数据进行处理以得到最终结果。

知道更多:数据结构+算法=程序

“数据+算法=程序”这个等式是由著名的“数据结构+算法=程序”变形而的。它由Pascal之父、结构化程序设计的先驱Niklaus Wirth先生最早提出,它抽象地归纳了一个程序的最核心内容是其中的用于表达数据的数据结构和对数据进行处理的算法。而咱们在这里提出的“数据+算法=程序”,则是具体地描述了一个程序由它要处理的数据以及对数据进行具体处理的算法共同组成。两个等式都是正确的,只是描述程序的角度不一样而已。

数据和算法伴随个人一辈子。在小小的HelloWorld.exe中,也一样有数据和算法的存在。例如,向屏幕输出“Hello World!”的语句:

cout<<"Hello World!"<<endl;

其中,“Hello World!”是一个要向屏幕输出的字符串数据。整个语句则表明了对这个字符串数据的处理:将字符串显示到屏幕上。数据和算法老是这样如影随行,成为我终身要完成的两大任务。

 

原文地址:http://www.cnblogs.com/nihaoCPP/p/3948513.html

相关文章
相关标签/搜索