C语言数据存储区

C语言数据存储主要涉及栈、堆和静态存储区。栈用于函数调用,保存参数、局部变量和返回地址;堆提供动态内存分配,适用于需要在函数外部保留的数据;静态存储区存放全局变量和静态局部变量,其生命周期贯穿程序始终。函数调用过程中,栈通过ebp和esp指针管理,避免野指针错误。堆管理通过malloc和free函数,不同系统采用不同管理策略。

C语言数据存储区主要分为栈、堆、静态存储区三个。

1.程序中的栈

栈在程序中用于维护函数调用上下文,且函数中的参数局部变量存储在栈上。可以这么认为,没了栈的话,程序几乎无法运行,这里的栈和数据结构里的栈是一个概念,但表现形式不一样。栈遵循后进先出原则,栈是一种后进先出的行为。

补充一下
(1)ebp是当前函数的存取指针,即存储或者读取数时的指针基地址。
(2)esp是当前函数的栈顶指针
每一次发生函数的调用(主函数调用子函数)时,在被调用函数初始时,都会把当前函数(主函数main)的EBP压栈,以便从子函数返回到主函数时能够获取EBP。ESP就是一直指向栈顶的指针,而EBP仅仅是存取某时刻的栈顶指针,以方便对栈的操作,如获取函数參数、局部变量等。

将内存看成是一个栈:
在这里插入图片描述

为什么说函数调用的时候,少不了栈呢?
这是因为,在调用函数的时候,需要去维护一个活动记录,如下图所示,栈保存了一个函数调用所需的维护信息,包括了参数,返回地址,局部变量,调用上下文等,其他数据信息指的是临时变量等。

在这里插入图片描述

函数调用过程

每次函数调用都对应着一个栈上的活动记录,调用函数的活动记录位于栈的中部,被调函数的活动记录位于栈的顶部。
在这里插入图片描述
如上图,main函数中调用其他函数,先要将main函数压栈,再调用函数f,函数f里有函数g,那么函数f也被压栈,栈顶指针指向的为函数g。

函数调用的栈变化1
从main函数开始运行,栈就有了main函数的活动记录
在这里插入图片描述
函数调用的栈变化2
main函数调用函数f,在栈上又建立了函数f的活动记录。
栈顶指针发生了移动,函数f活动记录中的返回地址其实就是没调用函数f前main函数栈顶指针的地址,old ebp保存的是上一次ebp指针保存的位置。
新老活动记录通过ebp指针来关联,ebp指针向上读四个字节,就读取到了栈顶指针需要返回的地址,所以说ebp指针的作用就是进行函数调用的返回。
在这里插入图片描述
函数调用的栈变化3
当从函数f返回主函数main
就通过ebp指针获取返回地址给栈顶指针,栈顶指针就指向了之前的位置,然后可以继续执行下一次的函数调用。

在这里插入图片描述
到这里我们可以发现到,栈空间里的数据,不会由于函数的返回而立即改变,函数返回仅仅是修改了ebp指针和esp指针的地址值,并没有去改变栈空间里面的数据,所以我们千万不要返回函数内部的局部变量的地址,也不要返回局部数组,这是因为栈空间里面的数据不会由于函数的返回而改变。如果我们在返回main函数之后,又调用了其他的函数,那么之前保存的函数f的活动记录就会被另一个函数使用了,这一部分内容就会发生变化。所以返回一个局部变量地址或者局部数组是没有意义的,因为已经不存在了,这样就会造成野指针的错误,而造成程序运行的崩溃。

函数调用栈上的数据
函数调用时,对应的栈空间在函数返回前是专用的;
函数调用结束后,栈空间将被释放,数据不再有效。
在这里插入图片描述

程序来说明

#include <stdio.h>

int* g()
{
    int a[10] = {0};
    
    return a;
}

void f()
{
	int i = 0;
	int b[10] = {1,2,3,4,5,6,7,8,9};
    int* pointer = g();
	
	for(i = 0;i<10;i++)
	{
		b[i]  =pointer[i];
	}
	for(i = 0;i<10;i++)
	{
		printf("%d\r\n",b[i]);
	}
}

int main()
{
    f();
    
    return 0;
}


编译运行,结果却是发生了段错误。注意给出的告警信息,就是那里出了问题,返回了局部变量的地址。函数f还调用了printf打印函数,把函数g的活动记录给清除了,原来的那些数组就被销毁了,没有意义了,这就是所谓的野指针。
在这里插入图片描述
为什么有了栈还需要堆?

如果我们确实需要一块空间来完成程序,例如在数据结构中采用空间换时间,提高效率那么就需要开辟一段新空间。
栈上的数据在函数返回后就会被释放掉,无法传递到函数外部,如∶局部数组

2.程序中的堆

概念:堆是程序中一块预留的内存空间,可由程序自由使用,堆中被程序申请使用的内存在被主动释放前将一直有效。C语言程序中通过库函数的调用获得堆空间

  • 头文件:malloc.h
  • malloc :以字节的方式动态申请堆空间
  • free :将堆空间归还给系统

不同的系统西面,对堆的管理方式是不一样的,系统对堆空间的管理方式主要有三种:空闲链表法,位图法,对象池法等等。

以空闲链表法为例,C语言以高效原则来设计,向堆区申请内存的时候也要高效,系统就会将堆空间组织成一个链表,方框表示的是每个节点的内存空间的大小,例如我们要申请4字节的内存,而5字节内存的节点被申请完了,那么就会到最近的12字节节点来申请内存空间。
在这里插入图片描述

3.程序中的静态存储区

  • 静态存储区随着程序的运行而分配空间
  • 静态存储区的生命周期直到程序运行结束
  • 在程序的编译期静态存储区的大小就已经确定
  • 静态存储区主要用于保存全局变量和静态局部变量
  • 静态存储区的信息最终会保存到可执行程序中

静态存储区的验证:

#include <stdio.h>

int g_v = 1;

static int g_vs  = 2;

void f()
{
    static int g_vl = 3;
    
    printf("%p\n", &g_vl);
}

int main()
{
    printf("%p\n", &g_v);
    
    printf("%p\n", &g_vs);
    
    f();
    
    return 0;
}

编译运行结果如下,可以发现内存空间是连续的,以四字节之差连续存储三个变量。
在这里插入图片描述

4.小结

  • 栈,堆和静态存储区是程序中的三个基本数据区
  • 栈区主要用于函数调用的使用
  • 堆区主要是用于内存的动态申请和归还
  • 静态存储区用于保存全局变量和静态变量
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值