窗体 局部变量转换为全局_从嵌入式编程中感悟「栈」为何方神圣?
ID:技术让梦想更伟大
作者:李肖遥
何为变量?
变量一般可以细分为如下图:
本节重点为了让大家理解内存模型的“栈”,暂时不考虑“静态变量” 的情况,并约定如下:
“全局变量”仅仅默认为“普通全局变量”;
“局部变量”仅仅默认为“普通局部变量”。
如何判定全局变量和局部变量?
简单直观的来说,全局变量就是在函数外面定义的变量,局部变量就是在函数内部定义的变量,下面的例子能很清晰地说明全局变量和局部变量的判定方法:
unsigned char a; //在函数外面定义的, 所以是全局变量。void main() //主函数{ unsigned char b; //在函数内部定义的, 所以是局部变量。 b=a; while(1) { }}
全局变量和局部变量的内存模型
单片机内存包括ROM和RAM 两部分,ROM存储的是单片机程序中的指令和一些不可更改的常量数据,而 RAM存放的是可以被更改的变量数据;
也就是说,全局变量和局部变量都是存放在RAM,但是,虽然都是存放在 RAM,全局变量和局部变量之间的内存模型还是有明显的区别的。
因此,分了两个不同的RAM区,全局变量占用的 RAM区称为全局数据区, 局部变量占用的 RAM 区称为栈。
它们的内存模型到底有什么本质的区别呢?
全局数据区就像你自己家的房间,是唯一的,一个房间的地址只能你一个人住(假设你还是单身狗的时候),而且是永久的(sorry),所以说每个全局变量都有唯一对应的 RAM 地址, 不可能重复的。
栈就像客栈, 一年下来每天晚上住的人不一样,每个人在里面居住的时间是有期限的,不是长久的,一个房间的地址一年下来每天可能住进不同的人,不是唯一的。
全局数据区的全局变量拥有永久产权,栈区的局部变量只能临时居住在宾馆客栈, 地址不是唯一的, 有期限的。
栈是给程序里所有函数内部的局部变量共用的,函数被调用的时候,该函数内部的每个局部变量就会被分配对应到栈的某个RAM 地址,函数调用结束后,该局部变量就失效。
因此它对应的栈 的RAM空间就被收回,以便给下一个被调用的函数的局部变量占用。
举例借用“宾馆客栈”来比喻局部变量所在的“栈”。
void function(void); //子函数的声明void function(void) //子函数的定义{ unsigned char a; //局部变量 a=1;} void main() //主函数{ function() ; //子函数的调用}
我们看到单片机从主函数 main 往下执行, 首先遇到function()子函数的调用, 所以就跳到function()函数的定义那里开始执行, 此时的局部变量 a 开始被分配在 RAM的“栈区” 的某个地址, 相当于你入住宾馆被分配到某个房间。
单片机执行完子函数function() 后,局部变量 a 在 RAM 的栈区所分配的地址被收回, 局部变量a 消失,被收回的RAM地址可能会被系统重新分配给其它被调用的函数的局部变量。
此时相当于你离开宾馆,从此你跟那个宾馆的房间没有啥关系, 你原来在宾馆入住的那个房间会被宾馆老板重新分配给其他的客人入住。
全局变量的作用域是永久性不受范围限制的,而局部变量的作用域就是它所在函数的内部范围。全局变量的全局数据区是永久的私人房子,局部变量的栈是临时居住的客栈。
总结如下
- 每定义一个新的全局变量,就意味着多开销一个新的RAM 内存。而每定义一个局部变量,只要在函数内部所定义的局部变量总数不超过单片机的栈区,此时的局部变量不开销新的 RAM内存, 因为局部变量是临时借用栈的, 使用后就还给栈,栈是公共区, 可以重复利用,可以服务若干个不同的函数内部的局部变量。
- 单片机每次进入执行函数时,局部变量都会被初始化改变,而全局变量则不会被初始化, 全局变量是一直保存之前最后一次更改的值。
有哪些常见疑问?
全局数据区和栈区是谁在幕后分配的, 怎么分配的?
是C编译器自动分配的, 至于怎么分配,谁分配多一点,谁分配少一点,C 编译器会有一个默认的比例分配, 我们一般都不用管。
栈区是临时借用的,子函数被调用的时候,它内部的局部变量才会“临时” 被分配到“栈” 区的某个地址,那么问题来了,谁在幕后主持“栈区” 这些分配的工作?
单片机已经上电开始运行程序的时候,编译器已经不起作用,“栈区” 分配给函数内部局部变量的工作,确实是 C 编译器做的,但这是在单片机上电前。
C 编译器就把所有函数内部的局部变量的分配工作就规划好了,都指定了如果某个函数一旦被调用,该函数内部的哪个局部变量应该分到“栈区” 的哪个地址,C 编译器都是事先把这些“后事” 都交代完毕了才结束自己的生命。
等单片机上电开始工作的时候,虽然C编译器此时不在了,但是单片机都是严格按照C编译器交代的遗嘱开始工作和分配“栈区”的。因此,“栈区” 的“临时分配” 非真正严格意义上的“临时分配”。
函数内部所定义的局部变量总数不超过单片机的“栈” 区的 RAM 数量, 那, 万一超过了“栈” 区的 RAM数量, 后果严重吗?
这种情况专业术语叫爆栈。程序会出现莫名其妙的异常,后果特别严重。
为了避免这种情况, 一般在编写程序的时候, 函数内部都不能定义大数组的局部变量, 局部变量的数量不能定义太多太大,尤其要避免刚才所说的定义开辟大数组局部变量这种情况。
大数组的定义应该定义成全局变量,或者定义成 静态的局部变量。
有一些C编译器,遇到“爆栈” 的情况,会好心跟你提醒让你编译不过去,但是也有一些 C 编译器可能就不会给你提醒,所以大家以后做项目写函数的时候,要对爆栈心存敬畏。
全局变量和局部变量的优先级
刚才说到,全局变量的作用域是永久性并且不受范围限制的,而局部变量的作用域就是它所在函数的内部范围。
那么问题来了,假如局部变量和全局变量的名字重名了,此时函数内部执行的变量到底是局部变量还是全局变量?
这个问题就涉及到优先级。
注意,当面对同名的局部变量和全局变量时,函数内部执行的变量是局部变量,也就是局部变量在函数内部要比全局变量的优先级高。
我们来举一些例子
请看下面第一个例子
unsigned char a=5; //此处第 1 个 a 是全局变量void main() //主函数{ unsigned char a=2; //此处第2个a是局部变量,跟上面全局变量的第1个a重名了 print(a); //把a发送到电脑端的串口助手软件上观察 while(1) { }}
正确的答案是 2。在函数内部的局部变量比全局变量的优先级更加高。
虽然这里的两个a重名了, 但是它们的内存模型不一样,第1个全局变量的a是分配在全局数据区,是具有唯一的地址的,而第2个局部变量的a是被分配在临时的栈区的,寄生在 main 函数内部。
再看下面第二个例子
void function(void); //函数声明unsigned char a=5; //此处第1个 a 是全局变量void function(void) //函数定义{ unsigned char a=3; //此处第 2 个 a 是局部变量。} void main() //主函数{ 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)。
再看下面第三个例子
void function(void); //函数声明unsigned char a=5; //此处第1个a是全局变量void function(void) //函数定义{ unsigned char a=3; //此处第2个a 是局部变量} void main() //主函数{ function(); //子函数被调用 print(a); //把a发送到电脑端的串口助手软件上观察 while(1) { }}
正确的答案是5。因为function这个子函数是被调用结束之后,才执行print(a)的,就意味function函数内部的局部变量(第2个局部变量)是在执行function(a)语句的时候就消亡不存在了。
同时,因为此时main函数内部也没有定义a的局部变量,所以此时function(a)的a是必然只能是第1个全局变量的a(在main函数外面定义的全局变量的a)。
最后
看到本文之后,相信大家已经对栈有了一些基础的认识,在嵌入式编程中,我们也要时刻注意,避免爆栈;如果有错误欢迎指出,我们下一期,再见。
窗体 局部变量转换为全局_从嵌入式编程中感悟「栈」为何方神圣?相关推荐
- 窗体 局部变量转换为全局_Unity 热更新解决方案 学习笔记(12)Lua 全局变量和局部变量...
笔者只是一位刚大三的学生本文章仅为学习笔记,非专业教程,仅供参考和学习交流!!! 如有错误或更好的方案欢迎指出和探讨!!! 全局变量 顾名思义,其生命有效期是全局的,整个lua文件中都可以使用,可以在 ...
- 汇编在嵌入式编程中的作用_如何在嵌入式Power BI报表中以编程方式传递凭据
汇编在嵌入式编程中的作用 In the article, How to embed a Power BI Report Server report into an ASP.Net web applic ...
- 嵌入式编程中的堆栈溢出检测
在嵌入式编程中,栈是一个很重要的概念,不管是裸机编程还是基于RTOS编程.函数形参.局部变量.函数调用现场的保护及返回地址.中断函数执行前线程保护及中断嵌套的现场的保护都依赖于栈空间.栈空间不足,程序 ...
- 嵌入式编程中volatile的重要性
1.引言 volatile影响编译器编译的结果输出,volatile 变量是随时可能发生变化的,与volatile变量有关的运算,不要进行编译优化,以免出错,(VC++ 在产生release版可执行码 ...
- 关于嵌入式编程中的uint8_t、uint16_t......
在嵌入式编程中经常遇到用uint8_t.uint16_t.uint32_t.uint_fast16_t之类的关键字定义一些整型变量,但是具体表示什么意思,并不是太清楚,只是把它当成int之类的整型变量 ...
- 、简述global关键字的作用_在C#编程中global关键字的作用及其用法
在C#编程中,global 是 C# 2.0 中新增的关键字,理论上说,如果代码写得好的话,根本不需要用到它.今天就为大家展示下global关键字的作用及其用法,希望对大家学习C#编程有所帮助. 假设 ...
- shell 不等于_关于shell编程中的整数值比较的两种方式的简单操作实例
谈一谈关于shell编程中的整数值比较的两种方式 Shell编程有时处理一个对象时,需要我们对对象进行测试. 只有符合要求的才采取下一步操作,这样做的好处可以避免程序出错. 这个测试的对象可以是文件. ...
- if __name__ == __main__:什么意思_秒懂Python编程中的if __name__ == 'main' 的作用和原理...
来源:菜鸟分析 链接: https://zhuanlan.zhihu.com/p/34112508 一天偶然发现知乎上有篇关于对python编程中的if __name__ == 'main'的理解陈述 ...
- java程序设计专业介绍_简介Java编程中的Object类
这篇文章主要介绍了简介Java编程中的Object类,是Java入门学习中的基础知识,需要的朋友可以参考下 Object 类位于 java.lang 包中,是所有 Java 类的祖先,Java 中的每 ...
最新文章
- 计算机控制炉温实验,计算机控制(炉温控制)实验报告.doc
- 堆栈——Windows核心编程学习手札之十八
- 结束SQL阻塞的进程
- php显示网卡信息,netwox显示网络配置信息
- 号称迄今为止最快,.NET6带来了什么?
- mysql 5.5.23 winx64,win10下mysql 5.7.23 winx64安装配置方法图文教程
- es和oracle,Oracle和Elasticsearch数据同步
- 华为为何还没鸿蒙,华为鸿蒙2.0正式发布,但无第三方公开支持,华为的路该怎么走?...
- 关于matlab浮点转定点总结
- linux目录常用命令
- caffe---验证码识别
- AJAX框架大全 (AJAX Frameworks)
- 主机箱前置耳机插孔没有声音的解决方案
- Activiti7使用
- 基于蚁群算法的多配送中心的车辆调度问题的研究(Matlab代码实现)
- Matlab中grid 的使用
- mysql中column的用法_关于MySQL的一些用法
- Ipad项目中用到的UIModalPresentationFormSheet,点击阴影部分dismiss 当前presented的controller
- pv 、uv、ip、vv、cv分别是什么
- matplotlib 给坐标轴上的数字加单位
热门文章
- 拼多多的真实面试题:数亿的用户,如何用Redis统计独立用户访问量
- MySQL 索引的问题
- 关于pagehelper分页
- 网络与IO知识扫盲(五):从 NIO 到多路复用器
- C# 类的派生 输出个人信息
- Leet Code OJ 8. String to Integer (atoi) [Difficulty: Easy]
- include php 失效,为什么include(‘php:// input’)不起作用?
- 极光推送经验之谈-Java后台服务器实现极光推送的两种实现方式
- 牛客 -- leetcode -- max-points-on-a-line
- 关于华为P40登录谷歌闪退的问题