C++内存的5个区:
(⊙v⊙)嗯,其实这5个区没啥好说的。但是为了之后要写的内容,先把这5个区简单说一下嘛。
栈:就是那些由编译器在需要的时候分配,在不需要的时候自动清楚的变量的存储区。里面的变量通常是局部变量、函数参数等。
堆:就是那些由new分配的内存块,他们的释放编译器不去管,由我们的应用程序去控制,一般一个new就要对应一个delete。如果程序员没有释放掉,那么在程序结束后,操作系统会自动回收。
自由存储区:就是那些由malloc等分配的内存块,与和堆是十分相似的,不过它是用free来结束自己的生命的。
全局/静态存储区:全局变量和静态变量被分配到同一块内存中,在以前的C语言中,全局变量又分为初始化的和未初始化的,在C++里面没有这个区分了,他们共同占用同一块内存区
常量存储区:这是一块比较特殊的存储区,他们里面存放的是常量,一般是不允许修改的。
栈与堆
感谢
对于堆,我们首先要知道操作系统有一个记录空闲内存地址的链表。当系统收到程序的需要分配内存的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆节点,然后把该节点从空闲节点链表中删除,并将该节点的空间分配下去。另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样代码中的delete语句才能正确的释放本内存空间。还有,由于找到的堆节点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲的链表中。所以频繁的new/delete势必会造成内存空间的不连续,从而造成大量的碎片。
对于申请大小的限制。栈在windows下,栈是向低地址扩展的数据结构,是一块连续的内存区域。也就是说栈顶的地址和栈的最大容量是系统预先规定好的(好像VS上面是1M样),如果申请的空间超过栈的剩余空间,将会提示overflow。而对于堆来说,堆是向高地址扩展的数据结构,是不连续的内存区域。我们之前说过我们是通过空闲内存地址的链表的帮助来分配内存的,所以自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。
还有,在函数调用时,第一个进栈的是主函数中函数调用语句的下一条指令的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右到左入栈,然后是函数中的局部变量。注意静态变量是不入栈的。当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数的下一条指令,程序由该点继续运行。
有趣的小例子
int main(){
char* p="hello1";
char a[]="hello2";
p[2]='A';
a[2]='A';
return 0;
}
这个代码是有错的,p[2]=’A’错了。指针变量p和变量数组a都在栈区,”hello1”与”hello2”都是字符串常量在常量存储区,不过字符串常量”hello2”赋值给了一个局部变量(char []型数组)。字符串常量”hello2”在内存就有两份拷贝了,一个在动态分配的栈中,一个在常量存储区中。p[2]=’A’是对常量存储区中的字符串”hello1”进行修改,这肯定是不行的,而a[2]=’A’就可以了,它是对栈中的char型数组a进行修改。
再来一个例子
int main(){
char* p="hello1";
char a[]="hello2";
p++;
a++;
return 0;
}
这个代码也是有错的。a++错了,a是一个数组名,相当于一个常量指针不能被修改。
我们再次强调一下:
p是指向字符串常量首地址的指针,内容为常量不可修改,指针可以修改;
a是指向数组的常量指针,指针不可修改,内容可修改。
再来一个例子
char *returnStr1()
{
char *p="hello1";
return p;
}
char *returnStr2()
{
char p[]="hello2";
return p;
}
int main()
{
char *str1=NULL;
str1=returnStr1();
printf("%s\n", str1);
char *str2=NULL;
str2=returnStr2();
printf("%s\n", str2);
return 0;
}
“hello1”是字符串常量,被放在常量存储区,把该字符串常量存放在常量存储区的首地址赋给指针p,当returnStr1函数结束退出后,该字符串常量所在的内存不会被回收。而对于returnStr2函数中的局部变量char型数组p中存放的是字符串常量”hello2”赋值给的,这个是在动态分配的栈中,当returnStr2函数结束退出后,局部变量的内存被回收,这时候返回的地址是已经被释放的内存地址了。