教程 - 软件 - 文章 - 论坛

::白话C++::

基础-程序与内存(一)

数据的“生死”

请问,这是什么?

世界

(世界)

随手画的一个“圆”。这里用它来表示“世界”,或者说“人世间”。

请问,这又是什么?

生

(生:一个人来了这世界)

这是一个人出生了,来了这世界,开始他几十年的生涯。

死

(死:一个人离开了这世界)

没错,这回代表这个人死了:“他离开了这世界……”。

子路问孔子:“何为死?”,孔子生气了:“未知生,焉知死?”。现实生活中,我确实经常于坐马桶时,顺便摆出“思想者”的姿势,很臭屁地深思“人为什么活着?又为什么要死?”这样的问题。但我深知这类问题看似幼稚,其实深遂得没有答案;所以断断不敢在C++课程里妄讲这课题。不过让借此我来讲讲计算机程序的“生死”问题,我可以凑个数。

请问,这是?

内存

(内存)

这是内存。(有同学问为什么“内存”这么像梯子,这个问题请改为考虑“世界”为什么像个鸡蛋)。

继续请问,这又是什么?

数据生

(数据来到内存)

现在数据“生”,或者说“活着”,因为它进入内存的世界。

最后,这是什么?

程序“死”了

(数据“离开了”内存)

你当然可以说这又是“内存”,但根据上下文,同一张图这时表示数据离开内存,因此内存里又空空的没有数据了——它们来了,它们又走了。

人的生死,在医学判定上或者有“心死亡”或“脑死亡”等标准,但我们不管,只认为人来到世界,就是“生”,离开世界,不能在这世界上活动了,就是“死”。

计算机中有很多数据,它们的世界就是内存,它们的舞台就是内存。

比如你从网上下载了一首MP3的歌。当你不用听时,歌曲的数据保存在硬盘上,或者你的U盘上,或者刻在某张光盘上,这首歌都是“死”的。只有在你用像“千千静听”等播放器开始播放时,这首MP3才“活”过来。请记住:这时,MP3歌的数据,被(播放软件)加载到你的电脑的内存中。硬盘、光盘、U盘、软盘这些存储器叫“外存”。之所以“外”,倒不是因为说这四者中有三者一般是位于电脑机箱之外,而是因位它们位于CPU指令可以直接读写的范围之外。CPU,外存、内存的关系,我们后面讲。

其它的一样,比哪相片。如果你自拍了一张相片,如果你不想用看图软件打开欣赏,那么它们就是“死”的。

数据的加载

在我们来到世界之先,我们在哪里?在我们离开这世界之后,我们去了哪里?我们无从知道,更惶论“我们为什么要来,为什么要去”这类问题了。因为我们只是一段“数据”,一个“符号”,所以我们不知其然,更不知其所以然。问题的答案可能在上帝那里。

计算机的数据也如此。它们不会,也没有权力自动跑入内存,或离开内存。必须有“上帝”的存在。对于普通数据而言,它们的上帝就是程序。

程序当然更是如此。只不过普通数据是通过程序(或称为软件)加载到内存。而程序则是由操作系统来加载到内存中。我们列张表示意一下这层关系:

数据内容
由谁加载
音乐
音频播放器(比如:千千静听,WinAMP等)
图片
图片浏览器(比如文件管理器、ISee、ACDSee,Picasa2等)、或干脆由PhotoShop打开加开等
视频
视频播放软件,比如Windows MediaPlay,RealPlay,等等

(普通数据的加载)

程序也是数据

没错,对于计算机而言,一切都是数据。程序也是一种数据,一个程序的体内,往往也包括大量的普通数据及指令数据。因此程序本身也需要有它的上帝来负责它的生死。

数据内容
由谁加载
程序(也是一种数据)
操作系统(比如Windows XP或 Linux等)
操作系统(也是一种程序)
硬件

(程序数据的加载)

对于人来说,世界太重要了;对于程序来说,内存太重要了,因为内存就是程序赖于存活的世界。对于写程序的人,也就是程序员来说,世俗世界和内存世界一样重要;对于用C++程序员来说,内存有时甚至比世界还要重要:C++语言放心地把内存的生死大权交到程序员手里,如果一个C++程序员掌握不了内存释放与收回,那他往往会感觉到“生不如死”。

所以在我们第一节基础课,我们就开始讲内存。

作为开题的结束,我们来看一张图,这张图相对完整地画出文件、程序与内存、外存、CPU之间的关系,同时也示意了程序从生到死的过程。

CPU_内存_程序

(文件、程序、内存、外存、CPU)

(作业:请运行附件中的画笔程序,画一张图,并保存,然后重看本图。)

程序数据的分类

程序=指令+数据

现在我们了解到:程序在“活”的时候——这么说难免有些土——程序在运行的时候,它必须装载到内存中。我们了谈到:程序是一种特殊的数据。事实上程序包含一些指令和普通数据。怎么理解指令和数据的区别呢?普通数据就是电脑读了之后,可以“知道”一些信息,但不会依据这些信息去做点什么事;而指令则有针对特定的CPU而制定的特定信息,电脑的大脑“CPU”会依据这些指令而完成某些动作。我们来举个例子。

假设女朋友给你写了张纸条:“我爱你”。那我可以认为这是“数据”,因为或许你会因为这句话而感到幸福,或许你还会由此而决定去做些什么,但这并不是“我爱你”这句话给出的指示。

如果女友上面写的是“今晚7点,老地方见。”,那就是指令了,你该做的事情非常明确。

我们还知道,程序可以在运行时,从外部(比如外存)读入一些数据,或者输出一些数据。一个相对复杂的程序,不得不需要在运行时才从外部读入数据。

现在知道程序=指令+数据,现在综合考虑一下。

首先、“程序”是什么?

我们认为,程序就是把解决某个问题的思路或逻辑,用计算机语言表达出来,然后交给计算机去执行。这一点上说,程序很像人类的知识积累。一台电脑,如果它的程序越多,显然它能解决问题也越多。比如一台电脑有装杀毒软件,那么当电脑染毒时,就可以通过运行杀毒软件来解决。用人来比喻,一个婴儿,头脑里差不多只有一个“程序”:“哭”。不管饿了痛了,都运行“哭”这道程序。而换了大人呢,可选的程序就多了,叫外卖?下馆子?做饭?等等。

说到做饭,有句话叫“巧妇难为无米之炊”。既然是“巧”,我们可以认为:在“炊”这方面,该妇掌握了充足以及熟练的“指令”;但又为什么“难为”呢?因为“无米”。米就是“数据”。程序的本质就是根据一些“指令”去处理一些数据,二者缺一不可。所以才有这个等式:程序=指令+数据。

我们之前还谈过有关“做菜”的“程序”,或许大家可以做个比照。在这里《更多感受-Hello OO》;最初出处在:《第一感受——main 函数》

程序=指令、静态数据、动态数据

接下来,我们再根据获得数据的时机,来对数据进行区分。

既然“程序”和人的“思路”有可比性,我们就继续用来的“思路”,来说明“数据”有几种机会可以获取。

问题1:“给女朋友打个电话”。

这个问题需要运行你大脑中“打电话”的程序。过程是:摘起话机,然后拔出号码——别以为这不是程序,我那70多岁的奶奶就不会这道程序——另外这个程序需你知道要拔出的号码。因为是女友的,所以号码就在你脑海里,所以你不需要从外部读入数据。

问题2:“给女朋友的亲友团打电话”。

表面上还是打电话程序,但这回你记不得所有人的电话,所以你从外存——也就是你口袋里掏出通讯录,然后你通过眼睛这个设备,扫描一个号码,瞬间存到脑里(内存是也),然后再拔出去。

这两个问题对比,说明什么?一个人要办成一件事,他需要有一定的记忆力,用于长久地记下一些数据。但显然他不需要把所有数据都死记在脑海里,很多数据可以动手时才从外部获得。这两者缺一不可。

程序也如此:有一些数据是程序编制时,就可以确定下来;还有一些数据,需要在程序运行时才从外部读入。我们可以称前者为“静态数据”,后者为“动态数据”。

程序被操作系统加载进内存时,包括了指令数据和“静态数据”;之后程序继续运行,之后它会根据指令,从外部读入一些数据(也是读入到内存)。

我们由此得出一个新的结论,我们分为两点描述:

1、当程序处于“死”的状态,也就是程序没有进入内存,仅仅是你硬盘上的一个文件时,它包含指令和静态数据。

2、当程序进入内存,变为“活”的状态时,程序分根据需要,从外部(比如硬盘)读入外部数据。这时候,我们才认为“程序=指令+静态数据+动态数据”。

程序在内存的映射

有了前面的2点式结论,有关程序在内存的映射——也就是程序在内存中长什么样子?这一问题就好回答了,这回我们来个3点式的:

1、内存必须为程序的指令分配空间

2、内存必须为程序的静态数据分配空间

3、内存必须为程序的动态数据预留空间

前两点都是“分配空间”,但关于“动态数据”,则为“预留空间”。这又是为什么呢?

因为对于一个写定的程序,它的指令数据和静态数据的大小,是固定的;动态数据则在程序运行期间才决定要读入什么数据,多少数据。

比如前面的提到的例子:“给女朋友的亲友团打电话”,今天亲友团可能是100人,一个月以后,可能就变化为200人了——这完全依赖于你女友的交际能力。

“预留”具体又是什么意思呢?大意就是程序需要内存时,它的“上帝”,也就是操作系统(OS)必须能给它。突当我们年轻时,当我们决定要去追妞时,我们就会父母说:“我准备谈恋爱啦,请你们在金钱能为我有所预留。”于是我们每一次伸手,爸妈就会满足我们——直到有一天,从来不懂程序的爸妈,突然向我们请教:“孩子,你懂有一种内存,叫做‘虚拟内存’吗?”

当操作系统不太好满足程序的内存需求时,它就会把一部数据暂时保存到硬盘上,然后通过一定手段来实现由外存模拟成内存,这就叫做“虚拟内存”。

钱对于人生很重要,内存对于每个程序都很重要,但最关心内存的,还是操作系统,因为它要负责太多的程序的生死。因此,一个有道德的程序,在自己需要时伸手要完内存之后,必须记得在不需要时,归还内存,千万不能占着内存不拉 屎。

对于由Java或C#等语言写的,由虚拟机(或类虚拟机)运行的程序,内存的释放动作,由虚拟机(或类虚拟机)实现;因此这类语言的程序员,在这方面很轻松,当然他们也失去了一些控制权。(我不喜欢自动档的车,你呢?)

对于C++程序,“自动回收”和“手动回收”两种动态内存同时存在,其中“手动回收”的意思,就是程序员必须写上特定的代码,才能把某一段内存释放给操作系统,内存的生杀大权所握在程序员手里,有了权力如何才不滥用?事实上这就是要求C++程序员必须具备很高的道德情操。反过来说,一个优秀的C++程序员,就必然是一个“有理想、有道德、有文化、有纪律”的“四有新人”……

有点跑题……最新的结论是:对于C++,它动态内存,又区分为两种:“自动回收”和“手动回收”。

让我们再列一个1,2,3:

1. 程序包括“指令”和“数据”

2. 数据分为“静态数据”和“动态数据”。其中“静态内存”是程序一开始就有,而“动态内存”则在运行时,根据需要读入到内存,并且动态分配它的大小。

3. “动态数据”又分为“自动回收”和“手动回收”两种。

存储指令的内存空间,我们也称为“代码段”。而为静态数据分配的空间,也称为“数据段”。存放“自动回收”的动态数据的内存空间,我们称为“栈空间”;存放“手动回收”的内存空间,我们称为“堆空间”。

存放内容 内存段命名
指令数据
代码段
静态数据
数据段
“自动回收”的动态数据
“手动回收”的动态数据

(程序的内存段命名)

我们也可以重新画一张程序的内存映射示意图了。当一个程序“活着”时,也就是说当这一个程序还存在于内存中时,它长这个样子:

内存映射

(程序的内存映射)

为什么我要在“堆”和“栈”两个位置各放一颗星星呢?因为,它们是我们下一节课的重点:下节课我们依然讲“程序与内存”。作为对比,本节课主要讲解一个完整程序的生与死,一个完整程序应用的内存数据;而下一节,我们主要讲一个程序在运行期时,也就这个程序活着的时候,它内部的“动态数据”的生死过程。

好累,下一节见!

版权所有 谢绝复制。作者:南郁(nanyu) www.d2school.com