解答这个问题的系统和编译器版本有两种,如下:
1)x86 32bit linux-redhat 2.6.32-279.el6.i686 gcc-4.4.6
2)win7 64bit 32bit-VS2013
其它系统的情况,请大家自行进行测试,原理都是相同的。
要考虑堆和栈的大小,需要先考虑以下几个问题:
1)当前系统进程虚拟地址空间有多大,用户空间和内核空间是怎么划分的
2)实际物理内存的大小
3)当前系统交换分区(虚拟内存)设置的大小
A.栈空间描述
对于栈空间的大小来说,在 linux 系统上大家是可以通过 ulimit -s 来查看默认的栈大小的,当然这个栈大小是可以通过命令来设置的,如 linux 系统上:
[mm@localhost ~]$ ulimit -s
10240
表示栈的默认大小是 10M,你在栈上定义一个 10M 大小的数组,是没有任何问题的,
如:
#define SIZE (1024*1024*10)
char array[SIZE] = {0};
但是有的同学说,我搞了个比 10M 大一点的栈数组,也没有问题啊,比如:
#define SIZE (1024*1024*11)
char array[SIZE] = {0};
好,你再增大一点,如下:
#define SIZE (1024*1024*12)
char array[SIZE] = {0};
编译,运行,结果如下:
[mm@localhost test]$ ./a.out
Segmentation fault (core dumped)
你没看错,发生段错误了,那为什么小范围超过 10M 的栈空间也是可以分配的呢?当我们在代码上分配空间的时候,不管是栈还是堆,其实都只是在虚拟地址空间上分配的内存空间,当真正进行读写使用的时候,随着不断的发生缺页异常,才会去分配真正的物理内存和虚拟地址空间上的页面进行映射。缺页异常处理函数在内核上是 do_page_fault,越界访问栈空间,内核会在一定范围对栈的空间进行增长的。作为研发,了解 linux 系统程序运行的情况最重要,windows 系统默认的栈大小是 1M,你还可以通过 VS 编译器来设置程序运行时栈的大小,如下:
解决方法:扩大栈空间的大小,VS 设置项目属性:项目->属性->链接器->系统->堆栈保留大小
注:这里填的是字节数,如果你想把他扩大为 2M 的话,1024*1024*2 = 2097152B.堆空间描述堆的大小和当前系统设置的虚拟内存大小息息相关,上面已经描述过,当我们分配内存空间时,没有真正分配过物理内存,只有等到读取指令或者读写数据时产生缺页异常,才会真正分配物理内存和虚拟页面进行映射,当然,物理内存页面在分配的时候,会有页面置换的操作,根据 LRU 最近最久未使用算法,把不经常使用的页面 page out 换出到交换分区当中,以在物理内存中腾出更多的页面给进程使用,因此,程序中可以分配出比物理内存更多的空间,因为有虚拟内存(交换分区)的存在,但是交换分区的大小不宜过大,它毕竟属于磁盘 I/O,读写效率是非常低的,过多的依赖使用 swap 交换分区,会导致系统运行效率哒哒下降,甚至卡顿,所以堆空间的大小和当前系统的物理内存大小,交换分区大小,栈的大小,所使用共享库的大小都息息相关,因为它们都属于用户空间部分,x86 32bit linux 系统默认给用户空间是 3G,所以,看以下实验:
在 linux 系统,可以用以下代码检测一下堆的大小,大约在 2.98G,如下:
unsigned int count = 0;
unsigned int block = 1024*1024;
while(1)
{
char *p = (char*)malloc(block);
if(p != NULL)
{
count++;
continue;
}
printf("%0.2f G\n", count*1.0/1024);
break;
}
以上代码放在 windows 上的 VS 下运行(注意代码运行以后,由于只分配,不释放,系
统会卡顿一段时间,无法操作,要有心理准备),我的编译器是 32bit vs2013,得到的堆上限
大概是 1.85G,因为在 windows 上,4G 空间默认是 2G 给用户空间,2G 给内核空间,所以
可分配的堆大小不像 linux 那么大了,而且肯定不会超过 2G。