vlambda博客
学习文章列表

C语言内存四区模型(栈区、堆区以及全局区、还有凑数的代码区)


内存四区,一个非常重要的知识点,搞懂了内存四区,才能更快的去搞懂指针。我们写的C语言代码,不夸张的说,都是直接或者间接的在操作内存。C语言之所以能够开发操作系统,就是指针的存在,而指针说白了就是地址,内存地址,指针变量说白了就是存储地址的变量。所以所以所以,指针和内存模糊搞不懂,就先暂停,别学指针,先去深入了解一下变量,对,就是那个在程序运行中可变的量,C语言最基础的变量。

 

你真的知道变量吗,难道别人在问你什么是变量的时候,只有一句“在程序运行中可变的量”?再问你什么是常量的时候,再加个不,“在程序运行中不可变的量”?没有其他的概念?如果只有这一两句从入门到精通书本上的话,怕是离从入门到放弃不远了。

 

我们先不唠内存四区,就先来了解了解变量,从变量引入内存

我们知道定义一个变量,最基本的需要考虑两部分,数据类型和变量名,为什么要有数据类型?数据类型的存在是什么意义?难道就是单独为了刷存在感?绝对不是的,之所以是关键字,就是因为它们很关键。


先定义一个变量 int i;我们sizeof一下这个变量名: sizeof(i); //4我们sizeof一下这个数据类型: sizeof(int); //4-----------------------------------
我们再定义一个变量 char c;我们再sizeof一下这个变量名: sizeof(c); //1我们再sizoof一下这个数据类型: sizeof(char); //1----------------------------------- 我们再再定义一个变量: double d;我们再再sizeof一下这变量: sizeof(d);  //8我们再再sizeof一下这个数据类型: sizeof(double); //8-----------------------------------
我们再再再定义一个变量:  double dd;我们再再再sizeof一下这变量:  sizeof(dd);  //8我们再再再sizeof一下这个数据类型: sizeof(double); //8

    

OK,以上是在同一台设备上的结果。

1、发现我们不管sizeof数据类型还是变量名,返回的字节大小都是该变量占用的内存大小。那么我们是否可以把这个变量的数据类型和变量名都来表示这块内存呢,同一块内存。


2、同一个数据类型,变量名随意改变,sizeof的结果还是相同的。

3、那么我们再深入一下,这个变量的内存大小,和变量名没有关系,和数据类型有关系,有木有get到???

 

4、那么我们可以把这个变量表示为一块内存,定义一个变量就是开辟一块内存。

 

5、我们要一块内存拿来玩,就需要定义一个变量,那么我们要多大的内存,这就需要告诉编译器。拿数据类型出来,就是告诉编译器我们要多大的内存。那么我们可不可以试着把数据类型定义为表示内存的大小呢,int类型表示4个字节内存大小,char字符类型表示1个字节内存大小,double类型表示8个字节内存大小。而这些数据类型本身不占用内存空间,但是在定义变量的时候,就会有空间,编译器就会给该变量分配内存空间。我们使用这个变量名就可以找到这个内存,进行取值和赋值。

 

C语言内存四区模型(栈区、堆区以及全局区、还有凑数的代码区)

 (好吧,这荔枝,确实有点生硬)


数据类型 变量名 = 值;


得出以下结论:

数据类型的本质:就是限定内存字节大小的别名。

变量名的本质:就是一块连续的内存的别名。

值就是这块内存中实际存储的内容。


C语言内存四区模型(栈区、堆区以及全局区、还有凑数的代码区)

 (忽略那不懂事的logo)


变量的内存大小是通过数据类型决定的

数据类型其实就是个模具,而变量就是根据该模具创建出来的一个实物,实体存在的一块内存,值就是这个内存中实际存储的内容。


所以数据类型本身不占用内存空间,但是在定义变量的时候,编译器就会给该变量分配内存空间。



OK,不再探讨变量,这些我们都懂, 点到为止,只可意会不可言传。

 


我是有底线的





下面进入标题,内存四区

 

了解内存四区的本质就是了解定义的变量、常量以及手动分配的内存,在哪个内存区中存储,什么时候开辟空间,作用域的范围多大,生命周期多长,什么时候分配和释放,能不能去使用这个变量,什么时候能使用,什么时候不能使用。

一个变量的内存四区模型,和该变量的作用域、生命周期都有着密不可分的关系。


上图: (别给我提什么画图工具、ps、xmind、cad,我只服我的txt文本)

C语言内存四区模型(栈区、堆区以及全局区、还有凑数的代码区)

(别看了,下面内存四区模型图都是文本)


内存四区分别为:

栈区stack
    由编译器自动分配释放,存放函数的参数值,局部变量的值等。

 

堆区heap
    程序员分配释放(动态内存申请与释放)。
    程序员不释放,可能会造成内存泄漏,程序结束时可能由操作系统回收。

 

全局区global
    也有人称为静态区static,和常量区。
    程序运行而自动分配内存,程序结束后由系统自动释放。

 

代码区code
    我们编写的C语言代码,都在代码区。
    主要存放函数体的二进制代码。

    这个区域我们不关心。

 

 


我们来逐个分析一下,栈、堆、全局


代码区就当做来凑数的

因为代码区存放的都是函数体的二进制代码。
系统管理的,我们望尘莫及的。
得不到的永远在骚动~

 


------------栈区分析------------


栈区内存大小:

很小,和堆相比太小了。
栈内存,也叫临时内存,就是临时存储一些变量,所以不会太大。

 

栈区存储的数据:

1、auto自动变量(也叫非静态局部变量

2、函数的形参

3、函数的返回值

 

栈内存主要用于局部变量和函数的传参,所以不需要太大。
太大的栈内存会导致时间效率和空间效率的大大降低。

            不可能拿空间换效率,永远不可能。

   

栈区内存的分配与释放:

分配:
     由编译器自动分配

释放:

    由编译器自动释放


什么时候分配和释放:
    在执行到该函数体中的局部变量时,在栈内存中,编译器会自动分配该变量数据类型大小的空间,给该变量。当该函数执行完毕,局部变量不再作用域范围内,生命周期结束,编译器自动释放为该变量原先分配的栈内存空间。


栈区存储特点:

1、 先进后出,先进的后出
3、 存储方向:从上往下,往低地址方向依次存储
     类似于弹夹模型,先入栈的,最后弹栈,最后入栈的,先弹栈。

   

栈中出现数组:

存储和栈相反。
整个数组按照栈存储,但是数组中的每个元素,则相反。从下标为0的元素开始,从低地址向高地址依次存储,从下往上。

栈的生长方向和数组内元素的存放方向相反

堆的生长方向和数组内元素的存放方向相同


如果函数的返回值是指针时:

则不能返回局部变量,也就是栈区中的内存地址。   
当函数指向完毕后,函数内部的栈空间自动回收,局部变量的栈区空间的内容未知,有可能还保留着之前的内容,有可能是乱码,不确定的内容。

 

所以函数返回值是指针时,返回栈内存地址时,内存中的内容是不确定的。
函数返回值是指针时,不能返回栈内存地址。

 

      

C语言内存四区模型(栈区、堆区以及全局区、还有凑数的代码区)

 



栈区内存四区模型:

void fun(){ int a = 10; //变量a int b = 20; //变量b int c = 30; //变量c int arr[5] = {1,2,3,4,5}; //数组arr}


C语言内存四区模型(栈区、堆区以及全局区、还有凑数的代码区)




 我也是有底线的



 

------------堆区分析------------


堆区内存大小:

很大,和系统的内存有关系了,系统会有所保留的给于我们有限的堆内存空间,要比栈大的多。

       

堆区存储的数据:
只要我们分配了一块堆内存空间,就可以存储任何数据。

 

堆区内存的分配与释放:

分配:

    由程序员手动进行分配
    分配的堆区内存,往往有一个栈区的指针变量进行指向。
    所以栈区和堆区的模型,往往可以理解为同时存在的。


释放:
     由程序员手动进行释放

       

什么时候分配和释放:
    想分配就分配。
    在我们需要一块堆内存的时候,把数据存储到堆内存,就分配堆内存。当这个分配的堆内存空间使用完毕后 需要及时的进行释放,以免造成内存泄漏。或者程序运行结束,系统自动回收。

 

堆区存储特点:

1、先进先出,后进后出

 

堆中出现数组:

存储和栈相反,和堆相同。

栈的生长方向和数组内元素的存放方向相反。堆的生长方向和数组内元素的存放方向相同。

 


C语言内存四区模型(栈区、堆区以及全局区、还有凑数的代码区)



堆区内存四区模型:

分配的堆区内存,往往有一个栈区的指针变量进行指向。
所以栈区和堆区的模型,往往可以理解为同时存在的。
int *getMem(){  int *temp = (int*)calloc(5,sizeof(int));  return temp;} int main(){  int a = 10; int *p = (int *)malloc(sizeof(int)); *p = 1;
int *arr = getMem(); arr[0] = 10; arr[1] = 20; arr[2] = 30; arr[3] = 40; arr[4] = 50;         free(p); free(arr); p = NULL; arr = NULL;
return 0;}


C语言内存四区模型(栈区、堆区以及全局区、还有凑数的代码区)



 我也是有底线的



 

------------全局区分析------------


全局区大小:

        是一个全局的,整个程序的全局,应该挺大的吧,你品,你细品,啊哈哈哈哈哈~


全局区存储的数据:

1、全局变量
2、静态变量( 静态全局变量,静态局部变量)
3、字符串常量

 

全局区内存的分配与释放:

分配:
     随着程序运行,而分配内存

 

释放:
     随着程序结束,而释放内存

       

什么时候分配和释放:
    程序加载的时候,编译器就会给全局区中的数据分配相应的内存大小。
    当程序结束的时候,就会自动释放(不释放也不行啊)。


全局区存储特点:

1、和堆内存一样,都上从下往上依次存储的

全局区也细分很多不同的区

    全局变量和静态变量的存储是存放在全局区中的静态区中。

    未初始化的全局变量和未初始化的静态变量在相邻的另一块区域,该区域在程序结束后由操作系统释放。

    初始化的全局变量和静态变量在一块区域。

    程序结束后由系统释放。

全局区一般只分静态区和常量区:

静态区:
     存储静态变量和全局变量

       

常量区:
     存储字符串常量的,数值常量的区域


常量区特点:

常量区具有唯一性,不能有相同的字符串常量。

如果程序中,多次定义了相同的字符串常量,并且在不同的函数体内,赋值给了不同的变量。其本质是只存在一个。

char *str1 = "abcd";char *str2 = "abcd";//通过这些指针变量的指向的内存地址,可以得出,是同一个。//不信你试试

 

出现相同字符串常量时的内部操作:

第一次定义该字符串常量的时候,加载到全局区的字符串常量区中。

再次定义该字符串常量的时候,系统会先去字符串常量区中找,是否定义过该字符串常量。
定义过,就直接拿来使用。
没有定义过,就定义。

所以,字符串常量,在内存中,是唯一的。     

 


需要注意的是:

字符串常量直接赋值给字符数组的情况,是把常量区中的字符串拷贝一份,赋值给字符数组,两者没有了关系,不存在指向。所以这就是为什么可以改变字符串的字符。

 

字符串常量直接赋值给指针变量的情况,是把常量区中的字符串常量的地址,赋值给指针,指针可以操作该内存。



C语言内存四区模型(栈区、堆区以及全局区、还有凑数的代码区)


 

全局区内存四区模型:

#include <stdio.h>
char *get_str1(){ char *p = "abc"; return p;}
char *get_str2(){ char *p = "abc"; return p;}
int a; //全局变量a
int main(){ char *p = NULL; char *q = NULL;
  static int b;  //静态局部变量
p = get_str1();   q = get_str2();   return 0;}

C语言内存四区模型(栈区、堆区以及全局区、还有凑数的代码区)



 我也是有底线的



 


OK,以上就是内存四区的总结和模型,最重要的就是堆内存和栈内存,一定要区分开它的存储方式。

 

写的代码,多用内存四区画出来,脑壳里要有比较清晰的认识。


最后最后,奉上小编当时学内存四区时画的几张图图:

C语言内存四区模型(栈区、堆区以及全局区、还有凑数的代码区)

C语言内存四区模型(栈区、堆区以及全局区、还有凑数的代码区)

C语言内存四区模型(栈区、堆区以及全局区、还有凑数的代码区)





------------我真的是有底线的-----------




(在线卑微求关注,别走啊,取消推送也行啊~)