教程 - 软件 - 文章 - 论坛

::白话C++::

基础-程序与内存(三)

本节我们将对前两节课做一个总结,并且在总结过程中提出新问题。

首先我们回顾有关数据定义的过程——在《基础-程序与内存(一)》中,我们称之为数据的“生死问题”。

先看简单的,即栈数据(或静态数据)的定义过程。

栈数据的定义过程

假设要定义一个“整型”的栈数据,并且数据名称为 i ,C++语句为:

int i;

这样一行语句,在程序运行时,实际产生的内存影响是什么?请看图示:

栈数据定义示意图

(栈数据定义示意)

下面解释图中①、②、③步。

①、“占用了一段内存空间……”

天下没有免费的午餐。现实世界中,你想娶个媳妇,媳妇这个数据,就要会求你去“准备”一套房子……大致上是类似的,程序里我们想加个个数据,也不是免费的,数据必然要占用内存空间。新的问题是:得占用多大空间?现实生活中,娶个胖点的媳妇,晚上睡觉时就必然要占用大一些的床上空间——这一点也有可比性:数据也是有分“胖瘦”的。(当然,媳妇的胖瘦只影响床铺大小,并不决定买房面积,不过媳妇的多少,就可以决定买房面积了。)

②、“这段内存空间有一个地址(就像门牌号)”

内存要接受管理,可以想到的第一个办法肯定是给它们编个号。街道有编号,楼房有编号,家也得有个门牌号。内存空间采用数字作为编号,这个编号,就称为“内存地址”。大体上,可以把内存空间想像成一排大小完全一样的抽屉,每个抽屉都有一个编号。抽屉就是最小的内存单位。新问题是:每个抽屉,也就是“最小内存单位”,可以存多少信息?

图中的内存地址仅用于示意,堆或栈内存地址的起始,以及增加方向,会与具体的实现环境相关。但这些细节将会被完全屏蔽在实现层上。我们在实际编程无需考虑这些区别。

③ “内存中随机地存了一个数。 ”

刚一听,这似乎很不合理啊?我买了一套房,怎么一开始这房里来随机住着一个人哪?

首先,我们要做个区分:如果是栈数据,那么一开始分配出来的内存,确实是还“住”着一个随机的数据。如果是静态数据,那么可以保证里面住着一个“0”。怎么理解呢?

房子不是有“新房”和“二手房”之分吗?一个程序需要内存了,它向谁伸手要内存?是向操作系统。事实上,操作系统给出的内存空间,都是“二手内存空间”。当你的栈数据分到了内存空间,高高兴兴地拎着行李入住时,它们惊讶地发现空间里住着一个老数据。并且程序无从事先知道这个老数据是谁。

请看以下代码:

int i;

std::cout << "老数据 =" << i << std::endl;

i = 30;

std::cout << "当前数据 =" << i << std::endl;

这段代码执行时,我们可以确定第二行输出是30,而不能确定第一行输出是什么。这就是“随机”的意思。

如果你痛恨在你定了房间之后,还有人赖着住在那个房间里,那么你可以要求一定下房间,就立即登门入住。对应到C++语句,是这样的:

int i = 20;

赋值之后的内存示意图

(赋值之后的内存)

一行话,即申请了内存空间,又同时初始化了内存空间中的数据。当然,这样做,爽是爽矣,但它对我们提出附加的要求:你必须在定义这个数据时,就已经知道这个数据的值。我们经常租了一套房间,但不能立即入住,不是吗?典型的如:你朋友要从外地来你所在的城市,这种情况下,我们就得提前定下房间,等到朋友到达之后,他才会去入住。

我们似乎一直忽略“静态数据”。静态数据有两点主要不同:第一是生存期:程序运行时,就分配好空间,并且程序退出时,才归还空间。第二是初始化问题。可能是静态数据的身份似乎比较高贵,程序会在分配空间时,直接为把这段空间中的数据打干净——全部置为0,这个过程叫做“清零”。

有关栈数据与静态数据的定义过程,我们先总结到这里,下面讲“堆”数据。

堆数据的定义过程

假设要定义一个“整型”的堆数据,并且数据名称为 p ,C++语句通常可以分成两步:

第一步:

int * p;

该行语句在运行时,产生的内存效果示意如下:

堆数据定义第一步

(堆数据定义第一步:定义一个“特殊”栈变量)

你有没有玩过一种叫“找碴”的游戏? 现在请仔细对比上图与前面 “int i; 栈数据定义示意”那张图,然后找出其中3处不同……

嘿嘿,舍不得用鸡蛋砸我吧,现在物价这么贵。事实上不用对比,因为它们就是同一张图。

如果我们之前刚刚运行“int i;”并且i的地址是1310592,那么,紧接着运行“int * p;”,那么,显然p的地址不可能会又是1310592。没关第,这里的地址仅仅用于示意。

上一节我们提到:“堆数据不是单独存在,而是先结合一个栈数据或静态数据存在”。现在我们看到,定义一个堆数据的第一步,所产生的内存效果就是定义一个栈数据。本例中,这个数据的名字叫p,类型是 int* 。后面的星号,代表将通过它产生一个堆数据。也就第二步的内容。

第二步:

p = new int;

这行代码所产生的内存效果是:

分配堆内存示意图

(堆数据定义第二步:分配堆内存)

对比第一、二步的两张图,首先注意那个红色问号从“栈”内存转移到了“堆”内存中。栈内存中现在有了一个确切的值:84709772。其实它正是堆中新分配出来的那块内存的地址。

堆中新分配出来的内存中,存的是什么?正如图中第⑥步所说的,此时它也是随机的。如何为这块内存赋值呢?我们可以称为第三步:

*p = 20;

作业:请大家在前面示意图的基础上,画出本句的运行之后的内存效果。

堆数据定义的第一、二步,经常可以合成一句写成:

int * p = new int;

条件允许时,我们甚至连第三步也可以一起合并:

int * p = new int(20);


作业:

1. 不偷看课文,画出下面语句的内存效果示意图:

  • int i = 30;
  • int * p = new int ( 100 );

2. 重读上一节课。

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