0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

全局数据区和栈区是谁在幕后分配的?怎么分配的?

痞子衡嵌入式 来源:技术让梦想更伟大 2023-04-06 09:51 次阅读

何为变量?

变量一般可以细分为如下图:

fbfdb950-d40b-11ed-bfe3-dac502259ad0.png

本节重点为了让大家理解内存模型的“栈”,暂时不考虑“静态变量” 的情况,并约定如下:

“全局变量”仅仅默认为“普通全局变量”;

“局部变量”仅仅默认为“普通局部变量”。

如何判定全局变量和局部变量?

简单直观的来说,全局变量就是在函数外面定义的变量,局部变量就是在函数内部定义的变量,下面的例子能很清晰地说明全局变量和局部变量的判定方法:

unsigned char a;//在函数外面定义的,所以是全局变量。
voidmain()//主函数
{
 unsigned char b;//在函数内部定义的,所以是局部变量。
b=a;
while(1)
{

}
}

全局变量和局部变量的内存模型

单片机内存包括ROMRAM两部分,ROM存储的是单片机程序中的指令和一些不可更改的常量数据,而RAM存放的是可以被更改的变量数据;

也就是说,全局变量和局部变量都是存放在RAM,但是,虽然都是存放在RAM,全局变量和局部变量之间的内存模型还是有明显的区别的。

因此,分了两个不同的RAM区,全局变量占用的RAM区称为全局数据区, 局部变量占用的RAM区称为

它们的内存模型到底有什么本质的区别呢?

全局数据区就像你自己家的房间,是唯一的,一个房间的地址只能你一个人住(假设你还是单身狗的时候),而且是永久的(sorry),所以说每个全局变量都有唯一对应的 RAM 地址, 不可能重复的。

就像客栈, 一年下来每天晚上住的人不一样,每个人在里面居住的时间是有期限的,不是长久的,一个房间的地址一年下来每天可能住进不同的人,不是唯一的。

全局数据区的全局变量拥有永久产权,区的局部变量只能临时居住在宾馆客栈, 地址不是唯一的, 有期限的。

是给程序里所有函数内部的局部变量共用的,函数被调用的时候,该函数内部的每个局部变量就会被分配对应到的某个RAM地址,函数调用结束后,该局部变量就失效。

因此它对应的的RAM空间就被收回,以便给下一个被调用的函数的局部变量占用。

举例借用“宾馆客栈”来比喻局部变量所在的“栈”。

voidfunction(void);//子函数的声明

voidfunction(void)//子函数的定义
{
unsignedchara;//局部变量
a=1;
}

voidmain()//主函数
{
function();//子函数的调用
}

我们看到单片机从主函数 main 往下执行, 首先遇到function()子函数的调用, 所以就跳到function()函数的定义那里开始执行, 此时的局部变量 a 开始被分配在RAM的“栈区” 的某个地址, 相当于你入住宾馆被分配到某个房间。

单片机执行完子函数function()后,局部变量 a 在RAM的栈区所分配的地址被收回, 局部变量a 消失,被收回的RAM地址可能会被系统重新分配给其它被调用的函数的局部变量。

此时相当于你离开宾馆,从此你跟那个宾馆的房间没有啥关系, 你原来在宾馆入住的那个房间会被宾馆老板重新分配给其他的客人入住。

全局变量的作用域是永久性不受范围限制的,而局部变量的作用域就是它所在函数的内部范围。全局变量的全局数据区是永久的私人房子,局部变量的栈是临时居住的客栈。

总结如下

每定义一个新的全局变量,就意味着多开销一个新的RAM内存。而每定义一个局部变量,只要在函数内部所定义的局部变量总数不超过单片机的栈区,此时的局部变量不开销新的RAM内存, 因为局部变量是临时借用栈的, 使用后就还给栈,栈是公共区, 可以重复利用,可以服务若干个不同的函数内部的局部变量。

单片机每次进入执行函数时,局部变量都会被初始化改变,而全局变量则不会被初始化, 全局变量是一直保存之前最后一次更改的值。

有哪些常见疑问?

全局数据区栈区是谁在幕后分配的, 怎么分配的?

是C编译器自动分配的, 至于怎么分配,谁分配多一点,谁分配少一点,C 编译器会有一个默认的比例分配, 我们一般都不用管。

栈区是临时借用的,子函数被调用的时候,它内部的局部变量才会“临时” 被分配到“栈” 区的某个地址,那么问题来了,谁在幕后主持“栈区” 这些分配的工作?

单片机已经上电开始运行程序的时候,编译器已经不起作用,“栈区” 分配给函数内部局部变量的工作,确实是 C 编译器做的,但这是在单片机上电前。

C 编译器就把所有函数内部的局部变量的分配工作就规划好了,都指定了如果某个函数一旦被调用,该函数内部的哪个局部变量应该分到“栈区” 的哪个地址,C 编译器都是事先把这些“后事” 都交代完毕了才结束自己的生命。

等单片机上电开始工作的时候,虽然C编译器此时不在了,但是单片机都是严格按照C编译器交代的遗嘱开始工作和分配“栈区”的。因此,“栈区” 的“临时分配” 非真正严格意义上的“临时分配”。

函数内部所定义的局部变量总数不超过单片机的“栈” 区的 RAM 数量, 那, 万一超过了“栈” 区的 RAM数量, 后果严重吗?

这种情况专业术语叫爆栈。程序会出现莫名其妙的异常,后果特别严重。

为了避免这种情况, 一般在编写程序的时候, 函数内部都不能定义大数组的局部变量, 局部变量的数量不能定义太多太大,尤其要避免刚才所说的定义开辟大数组局部变量这种情况。

大数组的定义应该定义成全局变量,或者定义成静态的局部变量

有一些C编译器,遇到“爆栈” 的情况,会好心跟你提醒让你编译不过去,但是也有一些 C 编译器可能就不会给你提醒,所以大家以后做项目写函数的时候,要对爆栈心存敬畏。

全局变量和局部变量的优先级

刚才说到,全局变量的作用域是永久性并且不受范围限制的,而局部变量的作用域就是它所在函数的内部范围。

那么问题来了,假如局部变量和全局变量的名字重名了,此时函数内部执行的变量到底是局部变量还是全局变量?

这个问题就涉及到优先级。

注意,当面对同名的局部变量和全局变量时,函数内部执行的变量是局部变量,也就是局部变量在函数内部要比全局变量的优先级高。

我们来举一些例子

请看下面第一个例子

unsignedchara=5;//此处第1个a是全局变量

voidmain()//主函数
{
unsignedchara=2;//此处第2个a是局部变量,跟上面全局变量的第1个a重名了
print(a);//把a发送到电脑端的串口助手软件上观察
while(1)
{

}
}

正确的答案是 2。在函数内部的局部变量比全局变量的优先级更加高。

虽然这里的两个a重名了, 但是它们的内存模型不一样,第1个全局变量的a是分配在全局数据区,是具有唯一的地址的,而第2个局部变量的a是被分配在临时的栈区的,寄生在 main 函数内部。

再看下面第二个例子

voidfunction(void);//函数声明
unsignedchara=5;//此处第1个a是全局变量

voidfunction(void)//函数定义
{
 unsigned char a=3;//此处第 2 个 a 是局部变量。
}

voidmain()//主函数
{
 unsigned char a=2;//此处第 3 个 a 也是局部变量。
function();//子函数被调用
print(a);//把 a 发送到电脑端的串口助手软件上观察。
while(1)
{
}
}

正确的答案是2。因为,function这个子函数是被调用结束之后,才执行 print(a)的, 就意味函数内部的局部变量(第2个局部变量 a)是在执行 print(a)语句的时候就消亡不存在了, 所以此时print(a)的a是第3个局部变量的a(在 main 函数内部定义的局部变量的 a)。

再看下面第三个例子

voidfunction(void);//函数声明
unsignedchara=5;//此处第1个a是全局变量

voidfunction(void)//函数定义
{
unsignedchara=3;//此处第2个a是局部变量
}

voidmain()//主函数
{
function();//子函数被调用
print(a);//把a发送到电脑端的串口助手软件上观察
while(1)
{
}
}

正确的答案是5。因为function这个子函数是被调用结束之后,才执行print(a)的,就意味function函数内部的局部变量(第2个局部变量)是在执行function(a)语句的时候就消亡不存在了。

同时,因为此时main函数内部也没有定义a的局部变量,所以此时function(a)的a是必然只能是第1个全局变量的a(在main函数外面定义的全局变量的a)。






审核编辑:刘清

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 单片机
    +关注

    关注

    6001

    文章

    43973

    浏览量

    620847
  • ROM
    ROM
    +关注

    关注

    4

    文章

    524

    浏览量

    84812
  • RAM
    RAM
    +关注

    关注

    7

    文章

    1321

    浏览量

    113706
  • 嵌入式编程
    +关注

    关注

    0

    文章

    26

    浏览量

    10235

原文标题:从嵌入式编程中感悟「栈」为何方神圣?

文章出处:【微信号:pzh_mcu,微信公众号:痞子衡嵌入式】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    应大家要求详细讲解下C语言内存分配-通俗理解

    一次,如果反复,则需使用跳转指令,如果进行递归,则需借助来实现。代码包括操作码和要操作的对象(或对象的地址引用),如果是立即数(即具体的数值,如2),将直接包含在代码中;如果是局部数据,将在
    发表于 10-08 14:13

    C语言内存分配-通俗理解

    一次,如果反复,则需使用跳转指令,如果进行递归,则需借助来实现。代码包括操作码和要操作的对象(或对象的地址引用),如果是立即数(即具体的数值,如2),将直接包含在代码中;如果是局部数据,将在
    发表于 10-08 14:57

    请问stm32和堆的如何设置大小

    里边的设置的大小。现在的问题是我把和堆区分配的大小已经加大了过一段时间还是死机,那么
    发表于 12-17 08:48

    keilC51编译器在内存分配时知道避开模拟吗?

    程序中用了很多重入函数,假如程序中内存占用2K,我把模拟放在1K的位置,会不会出问题?编译器在内存分配的时候知不知道避开模拟呢?
    发表于 04-08 09:34

    全局变量数组分配分太多怎么办

    如果全局数组分配的太多太大会不会 跟 子函数里的数组有交集现象啊,我知道我的问题很白痴,一个在全局数据段 一个在里,位置都不一样,但是 通过运行后的
    发表于 05-29 04:35

    全局数组和全局变量之类的数据结构会对RTOS带来什么样的影响?

    最近在将一个协议移植到FreeRTOS系统上。之前的协议是基于前后台系统开发的,在串口接收中断将接收到的数据写入一个环形缓存,再根据协议从这个缓存
    发表于 06-13 09:00

    内存主要分为哪几个

    一、五大内存分区:内存分成5个,它们分别是堆、、自由存储全局/静态存储和常量存储。1
    发表于 07-15 07:33

    freertos与STM32如何分配堆栈空间

    freertos与STM32分析、堆、全局、常量、代码、RAM、ROM,及如何分配堆栈空
    发表于 08-03 06:36

    堆和的区别在哪

    以下引用网上资料 理解堆和的区别(1)(stack):由编译器自动分配和释放,存放函数的参数值、局部变量的值等,其操作方式类似于数据
    发表于 08-11 09:18

    stm32的代码和常量的地址分配在哪

    stm32的代码和常量的地址分配在哪?stm32的全局变量和堆栈的地址又分配在哪?
    发表于 12-02 06:05

    如何对RAM空间分配操作

    在代码编译过程中,编译器会根据配置和代码进行空间分配,包括对内存RAM的空间分配,对RAM空间分配操作,可以理解如下:分配全局变量区
    发表于 01-20 08:05

    STM32堆区划分

    STM32堆(一)一个由C/C++编译的程序占用的内存分为以下几个部分:(stack):编译器自动分配释放,存放函数的参数值,局部变
    发表于 01-20 08:32

    谈一谈单片机程序的与堆

    一、程序内存分配由c/C++编译的程序占用的内存分为以下几个部分1、(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于
    发表于 02-28 07:35

    求解ADS怎么把变量分配到程序code

    KEIL里面有code 和 data 关键词控制不知道ADS是怎么控制的呢?例如,我这样声明:const char n[] = {"这是怎么回事呢"};现在ADS把这个分配数据空间data,我要
    发表于 06-17 09:59

    static的全局变量与局部变量的使用,看完你就懂了

    在本文件中调用,不能被其他文件调用。static 修饰的变量存放在全局数据的静态变量,包括全局静态变量和局部静态变量,都在全局数据
    发表于 06-27 08:54