堆(Heap) 上面的工作是编译器做的,即程序员并不参与堆栈的维护。但上面已经说了,堆栈相当于在编译时期分配内存,因此一旦计算好某块内存的偏移,则这块内存就只能那么大,不能变化了(如果变化会导致其他内存块的偏移错误)。比如要求客户输入定单数据,可能有10份定单,也可能有100份定单,如果一开始就定好了内存大小,则可能造成不必要的浪费,又或者内存不够。
为了解决上面的问题,C++提供了另一个途径,即允许程序员有两种向操作系统申请内存的方式。前一种就是在栈上分配,申请的内存大小固定不变。后一种是在堆上分配,申请的内存大小可以在运行的时候变化,不是固定不变的。 那么什么叫堆?在Windows操作系统下,由操作系统分配的内存就叫做堆,而栈可以认为是在程序开始时就分配的堆(这并不准确,但为了不复杂化问题,故如此说明)。因此在堆上就可以分配大小变化的内存块,因为是运行时期即时分配的内存,而不是编译时期已计算好大小的内存块。 变量的定义 上面说了那么多,你可能看得很晕,毕竟连一个实例都没有,全是文字,下面就来帮助加深对上面的理解。 定义一个变量,就是向上面说的由编译器维护的变量表中添加元素,其语法如下: long a; 先写变量的类型,然后一个或多个空格或制表符(\t)或其它间隔符,接着变量的名字,最后用分号结束。要同时定义多个变量,则各变量间使用逗号隔开,如下: long a, b, c; unsigned short e, a_34c; 上面是两条变量定义语句,各语句间用分号隔开,而各同类型变量间用逗号隔开。而前面的式子5+2/3-5*2,则如下书写。 long a = 2/3, b = 5*2; long c = 5 + a – b; 可以不用再去记那烦人的地址了,只需记着a、b这种简单的标识符。当然,上面的式子不一定非要那么写,也可以写成:long c = 5 + 2 / 3 – 5 * 2; 而那些a、b等中间变量编译器会自动生成并使用(实际中编译器由于优化的原因将直接计算出结果,而不会生成实际的计算代码)。 下面就是问题的关键,定义变量就是添加一个映射。前面已经说了,这个映射是将变量名和一个地址关联,因此在定义一个变量时,编译器为了能将变量名和某个地址对应起来,帮程序员在前面提到的栈上分配了一块内存,大小就视这个变量类型的大小。如上面的a、b、c的大小都是4个字节,而e、a_34c的大小都是2个字节。 假设编译器分配的栈在一开始时的地址是1000,并假设变量a所对应的地址是1000-56,则b所对应的地址就是1000-60,而c所对应的就是1000-64,e对应的是1000-66,a_34c是1000-68。如果这时b突然不想是4字节了,而希望是8字节,则后续的c、e、a_34c都将由于还是原来的偏移位置而使用了错误的内存,这也就是为什么栈上分配的内存必须是固定大小。 考虑前面说的红色文字:“变量实际并不是一块内存,只是一个映射元素”。可是只要定义一个变量,就会相应地得到一块内存,为什么不说变量就是一块内存?上面定义变量时之所以会分配一块内存是因为变量是一个映射元素,需要一个对应地址,因此才在栈上分配了一块内存,并将其地址记录到变量表中。但是变量是可以有别名的,即另一个名字。这个说法是不准确的,应该是变量所对应的内存块有另一个名字,而不止是这个变量的名字。