关于《纠结的N皇后问题》中数组越界导致的sum出现非正常的变化这个问题,继而引发出关于内存到底是如何被分配的疑问,今天早上便着手进行探索,当然其中借鉴了广大网友的总结,其中包括birdzb,NEO等牛人关于这方面的讨论。特别是看到一些讨论,感触颇深啊http://www.programfan.com/club/showtxt.asp?id=191048。怎么说呢,我还是觉得研究一下是有必要的,但要注意适可而止。
知识储备:1. 内存的分区:代码区,数据区,堆,栈。 四个区域司职不同,相互配合。
- 变量的分类以及初始化情况:局部变量,全局变量,静态的,非静态的。C++里面又包括成员变量。
一. 局部变量
编译器按照内存地址递减的方式来给变量分配内存
局部变量很多书籍中也叫自动变量,它声明在函数块内,作用范围也在函数块内。 不能被同一源文件的其他函数使用,也不能被其他文件中的函数使用。局部变量存储在栈中。无论局部变量显示初始化,或者未初始化,都只有当定义它们的程序块被调用时(即执行时),才分配空间,声明或定义时并不分配。局部变量不是可执行模块的一部分!!除非显示地对局部变量进行初始化,否则,它们的初始值是不确定的。为了验证“编译器按照内存地址递减的方式来给变量分配内存”,我们进行如下实验:
测试一:
[cpp] view plain copy
<span style="font-size:16px;">#include "iostream"
using namespace std;
void main() {
int i,j;
cout<<&i<<' '<<&j<<endl;
}
测试结果是:
&i = 0012FF44, &j = 0012FF40
可见,虽然i在j之前被定义,但在编译器给变量分配内存时采用了内存地址递减的方式,所以j在内存中的位置比i超前了4个字节(因为是整型)。
测试二:
[cpp] view plain copy
#include "iostream"
using namespace std;
void main() {
int j,i;
cout<<&i<<' '<<&j<<endl;
}
测试结果是:
&i = 0012FF40, &j = 0012FF44
根据上面说明的采用内存递减的方式进行空间分配的话,首先分配的 j ,然后分配i,测试一首先分配i,然后分配j。
由此可见:在局部变量分配空间的顺序跟变量的声明顺序直接相关,同时按照内存由高到低的顺序进行空间分配。
下面看一段代码可以加深对数组越界出现昨天那个问题的理解。
[cpp] view plain copy
- int i,a[10];
- for(i=1;i<=10;i++)
- a[i]=0;
在for语句的比较部分本来是i<10;却写成了i<=10;因此实际上并不存在的a[10]被设置为0,也就是内存在数组a之后的一个字(word)的内存被设置为0。如果用来编译这段程序的编译器按照内存地址递减的方式来给变量分配内存,那么内存中数组a之后的一个字(word)实际上是分配给了整型变量i。此时本来循环计数器i的值为10,循环体内将并不存在的a[10]设置为0,实际上却是将计数器i的值设置为0,这就陷入死循环。
[cpp] view plain copy
- #include <iostream.h>
- void main()
- {
- int i,a[4];
- cout<<&i<<endl<<&a[0]<<' '<<&a[1]<<' '<<&a[2]<<' '<<&a[3]<<' '<<&a[4]<<endl;
- }
这段代码的执行结果:
0x0012FF44
0x0012FF34 0x0012FF38 0x0012FF3C 0x0012FF40 0x0012FF44
由此可以发现&i = &a[4],因为先声明的是i,然后才是a[4],故i处于高位,a处于低位,但是越界后a[4]来到高位,覆盖i
[cpp] view plain copy
- #include <iostream.h>
- void main()
- {
- int a[4],i;
- cout<<&i<<endl<<&a[0]<<' '<<&a[1]<<' '<<&a[2]<<' '<<&a[3]<<' '<<&a[4]<<endl;
- }
这段代码的执行结果:
0x0012FF34
0x0012FF38 0x0012FF3C 0x0012FF40 0x0012FF44 0x0012FF48
由于首先声明的是数组a[4],于是在高位上优先分配a,低位上分配i,因此可以发现这是并没有出现&i = &a[4]的情况。此时要是执行的话程序就不是假死了,直接是内存出错。
二. 全局变量
全局变量没有声明在任何一个函数内,作用范围在程序运行始终存在。能被同一源文件的任何函数使用,也能被其他文件中的函数使用,但要使用extern关键字。全局变量存储在数据段中。全局变量显示初始化时,或者未初始化时,在程序映像中有不同的分区:已初始化的全局变量是可执行模块的一部分。未初始化的全局变量则不是可执行模块的一部分,只有当定义它们的程序被调用时(即执行时),才分配空间,声明或定义时并不分配。未初始化的全局变量在运行时被初始化为0。
全局变量的空间一般分配在数据区,因此并不像局部变量那样在函数被调用的时候才按照声明顺序由递减的方式分配空间,全局变量在编译阶段就会把空间分配完毕,这其中的机制根据编译器的优化以及操作系统的原理都有很大的不同。
猜测一: 全局变量的显示初始为0与默认初始化为0效果一致,并不会导致内存分配地址出现不同,这时候遵循的规则是按章全局变量命名的字母顺序分配空间。
[cpp] view plain copy
- #include <iostream.h>
- int a;
- int c;
- int b;
- void main() {
- cout<<&a<<' '<<&b<<' '<<&c<<endl;
- }
0x0042E058 0x0042E05C 0x0042E060
[cpp] view plain copy
#include <iostream.h>
int c=0;
int a;
int b;
void main() {
cout<<&a<<' '<<&b<<' '<<&c<<endl;
}
0x0042E058 0x0042E05C 0x0042E060
由上述代码可以推测当所有的变量都默认初始化,或者显示初始化为0时,空间分配是按照声明变量的字母顺序按照空间递增顺序进行分配的。
添加数组测试用例:
[cpp] view plain copy
- #include <iostream.h>
- int a[2];
- int b;
- void main() {
- cout<<&a[0]<<' '<<&a[1]<<' '<<&a[2]<<endl<<&b<<endl;
- }
0x0042E058 0x0042E05C 0x0042E060
0x0042E060
[cpp] view plain copy
- #include <iostream.h>
- int b;
- int a[2];
- void main() {
- cout<<&a[0]<<' '<<&a[1]<<' '<<&a[2]<<endl<<&b<<endl;
- }
0x0042E058 0x0042E05C 0x0042E060
0x0042E060
可见两者的测试结果相同,说明的验证是正确的,作为默认初始化或者显示初始化为0的全局变量,其内存的分配与声明顺序无关,与变量命名的字母顺序有关。进一步测试:
[cpp] view plain copy
#include <iostream.h>
int c[2];
int b;
void main() {
cout<<&c[0]<<' '<<&c[1]<<' '<<&c[2]<<endl<<&b<<endl;
}
0x0042E05C 0x0042E060 0x0042E064
0x0042E058
[cpp] view plain copy
#include <iostream.h>
int b;
int c[2];
void main() {
cout<<&c[0]<<' '<<&c[1]<<' '<<&c[2]<<endl<<&b<<endl;
}
0x0042E05C 0x0042E060 0x0042E064
0x0042E058
数组也满足这样的猜测,其内存的分配按照变量名的字母顺序进行递增分配。
但是这时候要是有个有个变量初始化为0,结果就不按字母顺序进行递增分配了,而是按照声明顺序进行。
$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
上述猜测只对全局变量的命名为单字母成立,且字母的排序按照ASCII码中字母的顺序进行比较。如果全局变量的命名是多字母,或者数组中数值是宏并不是数字(若MAX = 4,那么a[MAX]被分配的地址与a[4]有可能不同),甚至因为代码的优化也会影响到地址的分配。这时候的规律是很难找的,而且也没有意义,最重要的是在自己写程序的时候尽量不要出现程序越界之后溢出覆盖的情形,那样是在太危险了。
不得不说别人说的很有道理,猜测一直接 陷入混乱,可能编译器的规则实在太多了,不能深究,掌握一些基本的就可以了。
$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
猜测二:全局变量初始化为非零的初始值时,内存分配按照声明顺序进行的
[cpp] view plain copy
#include <iostream.h>
int b=1;
int c=2;
int a=1;
void main() {
cout<<&a<<' '<<&b<<' '<<&c<<endl;
}
0x0042D708 0x0042D700 0x0042D704
[cpp] view plain copy
- #include <iostream.h>
- int a=1;
- int b=1;
- int c=2;
- void main() {
- cout<<&a<<' '<<&b<<' '<<&c<<endl;
- }
0x0042D700 0x0042D704 0x0042D708
由上述两个测试可以推测,当所有的全局变量初始化为非零值时,空间分配按照声明顺序按空间递增进行分配。
总结
事实上,全局变量不管有没有被初始化,其实都是被存放在DATA这个域中的,但是唯一不同的是这个DATA数据域有的时候又被划分成几个小的区域(说有的时候是因为并不是所有的系统都一定会这样做),分成initialized和un-initialized,因此,我们讨论的全局变量默认初始化或者初始化为零时,数据存储在un-initilized区域中,被初始化为非零时,数据存储在initialized区域中