首先理论知识
一个程序调用的内存主要被划分为4个区域,分别是栈区,堆区,数据区,代码区
首先来讲讲栈区:
- 栈区采用先进后出的数据结构,这也是为什么栈区叫栈区
- 自动局部变量、自动函数形参的内存空间都来自这个区域
- 而这个内存空间是在执行到变量定义语句才分配的,如果分配失败程序就崩溃了
- 而栈区的大小是受系统限制的,所以递归没有出口时就会一直占用栈区内存空间,直到栈区空间占满,造成栈溢出,使程序崩溃,所以递归效率低
- 一次隶属复合语句执行完空间就失效,下次复合语句被执行时空间在重新分配
(比如:调用一个函数,然后这个函数的自动局部变量已经函数的形参入栈,然后函数调用完毕后,这个函数在栈区的空间就被系统回收了(其实在汇编就是栈顶指针的偏移)) - 所以栈区内的内存空间的生存区由系统来设置,故称栈区内存空间的生存区为自动储存时期
然后我们来说堆区:
- 栈区的空间是由系统来支配的,那么堆区的空间就是由我们来支配了,欸嘿嘿
- 正因为堆区的空间是由我们来支配,所以堆区的内存空间的生存区为动态储存时期
- 由我们调用malloc函数或者在c++里可以用new运算符new出来(也就是向系统申请空间)
注意:刚分配出来的空间是野值,不可以直接使用 - 然后我们不用这块内存的时候就要用free函数(如果是malloc出来的)或者delete(new出来的)把其释放掉(即通知系统来回收)
- 这里要特别注意:!!!如果不用了一定要释放该内存空间!!!否则将造成内存泄漏,后果很严重!!!(
对系统来说,就相当于别人向你借了100万然后还不还一样) - 还要注意一定:同一块内存不要释放两次(
难道你想向别人借100万然后还200万 233,那么请务必向我借钱)
接下来是数据区:
- 数据区顾名思义,就是用来储存数据的
- 数据区又可化分为3个小区,分别是BSS段,普通数据区,只读数据区
- 其中BSS段储存着未初始化的或初始化为0的全局变量和static局部变量
- 数据区就储存着初始化为非零的全局变量和static局部变量(从这里就可以看出带了static关键字的局部变量储存方式其实完全和全局变量一样,只不过,它只可以在声明这个变量的空间内起作用)
- 所以要注意可读写的static变量要谨慎使用
- 然后只读数据区也是顾名思义,代表该区域的数据是只读的,不能被修改,所以一般字符串常量、带const的全局变量和static的局部变量
- 举个栗子
//在main函数内
char * str = "hallo"; //这里用一个指针变量str的内存空间存放在栈区里面占了sizeof(char *)个字节,而系统还分配了6个字节的空间给"hallo"字符串,这个字符串储存在数据区中的只读数据区
str[2] = 'a'; //这里如果我们修改字符串内存,编译器就会报错,所以这也说明了只读数据区是只读的。
- 但是如果是const修饰的全局变量我们去修改是可以通过编译的,编译器只是会给我们一个警告,但是程序运行起来就会崩溃了,所以我们千万不要去修改只读数据区的变量
- 举个栗子
#include <stdio.h>
const int x = 90;
int main()
{
int *ptr = &x;
*ptr = 233; //此时编译是可以通过的,但是程序运行到这句代码时就会崩溃
//所以我们尽量不要用指针去指向一个只读数据区的常量,如果实在要指向,一定要加上const关键字
//比如这样 const int *ptr = &x;
//此时如果在意图以指针间接修改x空间内的值,编译器将会报错
return 0;
}
- 所以,总结出只读数据区不能有写操作,有了直接崩溃
- 数据区还有以下特点
- 该区域里的内存空间在程序运行前期(在进入main函数之前)分配(如果内存不足程序无法运行),所以全局数据区不会有野值,如果变量未初始化将自动置为零然后储存在BSS段,如果加了const关键字就会储存在只读数据区
- 该区域里的内存空间在程序退出运行后释放,故可读写的全局变量不推荐使用
- 前一次的修改维持到下一次使用
然后,我们在来说说代码区:
- 顾名思义,所有函数对应的二进制指令均存放在本区域
- 所以的指令以函数为单位在本区域进行组织存放,程序中有多少函数,本区域就被分成多少份,没一份对应一个函数,因此函数名实际也代表函数在本区域所占空间的首地址
- 任何非NULL的函数指针均指向本区域的某一个函数
- 本区域只读,可执行
- 和数据区一样都是程序运行前分配(main函数以前),所以本区域的生存期为静态储存时期(也就是程序运行全期)
理论知识过后,我们来实践一下
我们来通过地址来看看,进程的内存分布
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char test1 = '1'; //数据区
char test5 = '5'; //
const char test2 = '2'; //数据区只读数据区
char test3; //数据区BSS段
void Code();
int main(int argc, char **argv)
{
system("color a");
static char test4 = 0;
char *ptr = "abcdefg";
char *ptr_h = (char *)malloc(1);
printf("\n----------------------------------------------------------\n\n");
printf("代码区:\n");
printf("main函数地址 %p\n", main);
printf("Daima函数地址: %p\n", Code);
printf("\n----------------------------------------------------------\n\n");
printf("栈区:\n");
printf("ptr的地址 %p\n", &ptr);
printf("ptr_h的地址 %p\n", &ptr_h);
printf("\n----------------------------------------------------------\n\n");
printf("数据区:\n");
printf("test1变量的存放空间 %p\n", &test1);
printf("test5变量的存放空间 %p\n", &test5);
printf("\n------------------------只读数据区---------------------\n");
printf("test2变量的存放空间 %p\n", &test2);
printf("\"abcdefg\"的存放空间 %p\n", ptr);
printf("\n----------------------bss段------------------------------\n");
printf("test3变量的存放空间 %p\n", &test3);
printf("test4变量的存放空间 %p\n", &test4);
printf("\n----------------------------------------------------------\n\n");
printf("堆区:\n");
printf("malloc申请来的地址 %p\n", ptr_h);
printf("\n----------------------------------------------------------\n\n");
free(ptr_h);
return 0;
}
void Code()
{
printf("wakaka!");
}
程序运行结果
接下来我们来验证,BSS段是不是存放了初始化为0的全局变量或局部变量
我们去修改test4的初始化
运行结果
然后我们将test4初始化为非0;
运行结果