你好,C++(4)2.1.3 我的父亲母亲:编译器和链接器 2.1.4 C++程序执行背后的故事
2.1.3 我的父亲母亲:编译器和链接器
从表面上看,我是由Visual Studio创建的,而实际上,真正负责编译源代码创建生成可执行程序HelloWorld.exe的却是Visual Studio中集成的C++编译器cl.exe和链接器link.exe。他们二老,才是我的亲生爹妈。
为了便于人们的编写、阅读和维护,我们的源文件是使用C++这种人们可以理解的高级程序设计语言编写的。然而,计算机却并不理解这种高级语言,也就无法直接执行高级语言编写而成的源文件。所以,这里就需要一个翻译的工作,将源文件中人们可以理解的C++高级语言翻译成机器可以理解执行的机器语言。我老爸编译器实际上是个翻译官,他的工作就是将用C++这种高级语言编写的源文件(.cpp)翻译成用计算机可以看懂的机器语言表示的目标文件(.obj),大家通常将这一过程称为编译。
在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.cpp
经过我爸我妈的编译链接过程,我从一个源文件(HelloWorld.cpp)变成了一个可执行文件(HelloWorld.exe),我就这样哇哇坠地了。整个过程,如图2-6所示:
图2-6 编译链接过程
2.1.4 C++程序的执行过程
一旦生成可执行文件,就可以给操作系统下达指令让文件开始执行。一个程序的执行是从其主函数开始的。但是在进入主函数开始执行之前,操作系统会帮我们做很多准备工作。比如,当操作系统接到执行某个程序的指令后,它首先要创建相应的进程并分配私有的进程空间;然后加载器会把可执行文件的数据段和代码段映射到进程的虚拟内存空间中;操作系统接着会初始化程序中定义的全局变量等。做好这些准备工作,程序就可以进入主函数开始执行了。
进入主函数后,程序会按照源代码给我制定的人生规划,一条语句一条语句地往下执行,一步一步地往下走。大家一定还记得,我的源代码是这样的:
- int main()
- {
- // 在屏幕上输出“Hello World!”字符串
- cout<<"Hello World!"<<endl;
- return ;
- }
从这里可以看到,进入主函数后,我的第一条语句就是:
- cout<<"Hello World!"<<endl;
这条语句的意思是让我在DOS窗口中显示“Hello World!”这样一串文字,于是我便开始控制DOS窗口,在其中显示这串文字,完成程序员通过这行代码交给我的任务。
接下来的一条语句是:
- return ;
这条简短的语句宣告了我人生历程的结束。它表示主函数的结束,整个程序执行完毕。图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,
- 00DC4EFA mov ecx,eax
- 00DC4EFC call dword ptr ds:[0DD0324h]
- 00DC4F02 cmp esi,esp
- 00DC4F04 call __RTC_CheckEsp (0DC132Ah)
- return ;
- 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!”是一个要向屏幕输出的字符串数据。整个语句则代表了对这个字符串数据的处理:将字符串显示到屏幕上。数据和算法总是这样形影不离,成为我终身要完成的两大任务。
你好,C++(4)2.1.3 我的父亲母亲:编译器和链接器 2.1.4 C++程序执行背后的故事的更多相关文章
- 你好,C++(25)函数调用和它背后的故事5.1.2 函数调用机制
5.1.2 函数调用机制 在前面的学习中,我们多次提到了“调用函数”的概念.所谓调用函数,就是将程序的执行控制权从调用者(某个函数)交给被调用的函数,同时通过参数向被调用的函数传递数据,然后程序进入 ...
- Golang基于学习总结
1.不支持继承 重载 ,比方C++Java的接口,接口的改动会影响整个实现改接口的类行为的改动,Go 设计者觉得这一特点也许根本没用. 2.必不论什么函数定义必须花括号跟在函数声明后面而不能换行 如 ...
- 第四章:Python基础の快速认识內置函数和操作实战
本課主題 內置函数介紹和操作实战 装饰器介紹和操作实战 本周作业 內置函数介紹和操作实战 返回Boolean值的內置函数 all( ): 接受一個可以被迭代的對象,如果函数裡所有為真,才會真:有一個是 ...
- win32 汇编学习(2):消息框
这一次,我们将用汇编语言写一个 Windows 程序,程序运行时将弹出一个消息框并显示"你好,我的第一个Win32汇编程序". 理论知识 Windows 为编写应用程序提供了大量的 ...
- Golang基础学习总结
转自:http://blog.csdn.net/yue7603835/article/details/44264925 1.不支持继承.重载 ,比如C++.Java的接口,接口的修改会影响整个实现改接 ...
- lua 5.3 英文手册 google机器翻译版
LUA Lua 5.3参考手册作者:Roberto Ierusalimschy,Luiz Henrique de Figueiredo,Waldemar Celes 版权所有©2015-2018 Lu ...
- 用户手册是Yasm汇编
本文档的用户手册是Yasm汇编. 它是介绍和通用所有Yasm用户参考. 英文的参考:http://www.cnblogs.com/coryxie/p/3959888.html 1 .介绍 Yasm b ...
- JSP应用开发 -------- 电纸书(未完待续)
http://www.educity.cn/jiaocheng/j9415.html JSP程序员常用的技术 第1章 JSP及其相关技术导航 [本章专家知识导学] JSP是一种编程语言,也是一种动 ...
- 什么是obj文件?
百度百科: 程序编译时生成的中间代码文件.目标文件,一般是程序编译后的二进制文件,再通过链接器(LINK.EXE)和资源文件链接就成可执行文件了.OBJ只给出了程序的相对地址,而可执行文件是绝对地址. ...
随机推荐
- C++中强制变换之const_cast
今天学习了一下C++中的强制转换,看了const_cast,我发现了这个转换关键字的奇怪之处,于是把它记录一下,废话不说,先看一个程序: #include <iostream> using ...
- Fire Net HDU 1045
简单深搜,可以完全暴力,不会超时的. #include<iostream> #include<cstring> #include<cmath> using name ...
- selenuim ide回放时出现的问题
[error] Unexpected Exception: fileName -> chrome://selenium-ide/content/selenium-core/scripts/htm ...
- centos升级openssh的两种方式
此文介绍的是服务器在有网络和无网络情况下升级openssh方式. 一.首先介绍一个无网络如何升级: 1.准备相关的包 openssh下载地址: http://mirror.internode.on. ...
- HDOJ 2071 Max Num
Problem Description There are some students in a class, Can you help teacher find the highest studen ...
- UVaLive5031 Graph and Queries(时光倒流+名次树)
题目链接:http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id=20332 [思路] 时光倒流+名次树(rank tree). 所谓“ ...
- zoj 1372
http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=1372 #include<iostream> #include& ...
- [转] 关于C++中模板中的typename和class的区别比较
C++箴言:理解typename的两个含义 转自http://blog.csdn.net/dick_china/article/details/4522253 问题:在下面的 template dec ...
- Selenium firefox 版本问题
问题:Unable to connect to host 127.0.0.1 on port 7055 after 45000 ms 原因: selenium-server-standalone-x. ...
- 山东如意路嘉纳高级定制西装品牌惊艳亮相intertextile面料展 - 服装资讯中心 - 华衣网
山东如意路嘉纳高级定制西装品牌惊艳亮相intertextile面料展 - 服装资讯中心 - 华衣网 山东如意路嘉纳高级定制西装品牌惊艳亮相intertextile面料展