从单片机基础到程序框架(全集 2019pdf版) - 第231页

第三个疑问 : 问:函数内部所定义的局部 变量总数 不超过单 片机的“栈” 区的 RA M 数量,那, 万一超过了“栈” 区 的 RAM 数量,后果严重 吗? 答: 后果特 别严 重。 这种情 况, 专业 术语叫 “爆 栈” 。 程序 会出现 异常 ,而且 是莫 名其 妙的异 常。 为了 避免这种情 况, 一般 在编写程序的 时候, 函数内部都不能 定义大 数组的局部变 量, 局部变量的数量不能定 义 太多太大, 尤其要避免刚 才所说的…

100%1 / 836
void HanShu(void) //子函数的定义
{
unsigned char a; //局部变
a=1;
}
void main() //主函
{
HanShu() ; //子函数的调用
}
分析:上述例子,单片机从主函数 main 往下执行,首先遇到 HanShu 子函数的调用,所以就跳到 HanShu
函数的定义那里开始执行,此时的局部变量 a 开始被分配在 RAM 的“栈区”的某个地址,相当于你入住宾馆
被分配到某个房间。单片机执行完子函数 HanShu 后,局部变 a RAM 的“栈区”所分配的地址被收回,
局部变量 a 消失,被收回的 RAM 地址可能会被系统重新分配给其它被调用的函数的局部变量,此时相当于你
离开宾馆,从此你跟那个宾馆的房间没有啥关系,你原来在宾馆入住的那个房间会被宾馆老板重新分配给其
他的客人入住。全局变量的作用域是永久性不受范围限制的而局部变量的作用域就是它所在函数的内部范
围。全局变量的“全局数据区”是永久的私人房子(这里的“永久”仅仅是举一个例子,别拿“70 年产权”
来抬杠),局部变量的“栈”是临时居住的“客栈”。重要的事情说两遍,再次总结如下:
(1)每定义一个新的全局变量,就意味着多开销一个新的 RAM 内存。而每定义一个局部变量只要在函
数内部所定义的局部变量总数不超过单片机的“栈”区,此时的局部变量不开销新的 RAM 内存,因为局部变
量是临时借用“”区的,使用后就还给“栈”“栈”是公共区,可以重复利用,可以服务若干个不同的函
数内部的局部变量。
(2)单片机每次进入执行函数时,局部变量都会被初始化改变,而全局变量则不会被初始化,全局变量
是一直保存之前最后一次更改的值。
【54.4 三个常见疑问。
第一个疑问
问:“全局数据区”和“栈区“是谁在幕后分配的,怎么分配的
答:是 C 编译器自动分配的,至于怎么分配,谁分配多一点,谁分配少一点,C 编译器会有一个默认的
比例分配,我们一般都不用管。
第二个疑问
问:栈”区是临时借用的,子函数被调用的时候,它内部的局部变量才会“临时”被分配到“栈”区
的某个地址,那么问题来了,谁在幕后主持“栈区”这些分配的工作,难道也是 C 编译器?C 编译器不是在
译程候一编译后就退台了序已单片
的时候,编译器此时还在幕后指手画脚的起作用?
答:单片机已经上电开始运行程序的时候,编译器是不可能起作用的。所以,真相只有一个栈区”
分配给函数内部局部变量的工作,确实 C 编译器做的,唯一需要注意的地方是,它不是“现炒现卖,而
是在单片机上电前,C 编译器就把所有函数内部的局部变量的分配工作就规划好了,都指定了如果某个函数
一旦被调用,该函数内部的哪个局部变量应该分到“栈区”的哪个地址,C 编译器都是事先把这些“后事”
都交代完毕了才“结束自己的生命”后面,等单片机上电开始工作的时候虽然 C 编译器此时“不在”了,
但是单片机都是严格按 C 编译器交代的“遗嘱”开始工作和分配“栈区”的。因此“栈区”的“临时分
配”非真正严格意义上的“临时分配”
第三个疑问
问:函数内部所定义的局部变量总数不超过单片机的“栈”区的 RAM 数量,那,万一超过了“栈”
RAM 数量,后果严重吗?
答:后果特别严重。这种情况,专业术语叫“爆栈”程序会出现异常,而且是莫名其妙的异常。为了
避免这种情况,一般在编写程序的时候,函数内部都不能定义大数组的局部变量,局部变量的数量不能定
太多太大,尤其要避免刚才所说的定义开辟大数组局部变量这种情况。大数组的定义应该定义成全局变量,
或者定义成“静态的局部变量静态”这部分相关的内容后面章节会讲到有一些 C 编译器,遇到“
栈”的情况,会好心跟你提醒让你编译不过去,但是也有一 C 编译器可能就不会给你提醒,所以大家以后
做项目写函数的时候,要对“爆栈”心存敬畏。
【54.5 全局变量和局部变量的优先级。
刚才说到全局变量的作用域是永久性并且不受范围限制的,而局部变量的作用域就是它所在函数的
部范围,那么问题来,假如局部变量和全局变量的名字重名了,此时函数内部执行的变量到底是局部变量还
是全局变量?这个问题就涉及到优先级。注意当面对同名的局部变量和全局变量时,函数内部执行的变
是局部变量,也就是局部变量在函数内部要比全局变量的优先级高。为了深刻理解“全局变量和局部变量的
优先级”,强烈建议大家必须仔细看完下面列举的三个练习例子。
【54.6 例程练习和分析。
请看下面第一个例子:
/*---C 语言学习区域的开始。-----------------------------------------------*/
unsigned char a=5; //此处 1 a 是全局变量
void main() //主函
{
unsigned char a=2; //此处第 2 a 是局部变量。跟上面全局变量的 1 a 重名了!
View(a); //把 a 发送到电脑端的串口助手软件上观察。
while(1)
{
}
}
/*---C 语言学习区域的结束。-----------------------------------------------*/
分析:
上述例子, 2 个变量重名了!其中一个是全局变量,另外一个是局部变量。此时输出显示的结果是 5
还是 2?正确的答案是 2。因为在函数内部,函数内部的局部变量比全局变量的优先级更加高。此时 View(a)
是第 2 局部变量的 a,而不是第 1 个全局变量 a。虽然这里的两 a 名了,但是它们的内存模型不
样, 1 个全局变量的 a 是分配在“全局数据区是具有唯一的地址的,而第 2 个局部变量的 a 是被分配在
临时的“栈”区的,寄生 main 函数内部。
再看下面第二个例子:
/*---C 语言学习区域的开始。-----------------------------------------------*/
void HanShu(void); //函数声明
unsigned char a=5; //此处 1 a 是全局变量
void HanShu(void) //函数定义
{
unsigned char a=3; //此处第 2 a 是局部变量。
}
void main() //主函
{
unsigned char a=2; //此处第 3 a 也是局部变量。
HanShu(); //子函数被调
View(a); //把 a 发送到电脑端的串口助手软件上观察。
while(1)
{
}
}
/*---C 语言学习区域的结束。-----------------------------------------------*/
分析:
上述例子, 3 个变量重名了!其中一个是全局变量,另外两个是局部变量。此时输出显示的结果是 5
还是 3 2?正确的答案 2。因为,HanShu 个子函数是被调用结束之后,才执行 View(a)的,就意味
HanShu ( 2 a)执行 View(a)候就在了
View(a)的 a 是第 3 个局部变量的 a(在 main 函数内部定义的局部变量 a)
再看下面第三个例子:
/*---C 语言学习区域的开始。-----------------------------------------------*/
void HanShu(void); //函数声明
unsigned char a=5; //此处 1 a 是全局变量
void HanShu(void) //函数定义
{
unsigned char a=3; //此处第 2 a 是局部变量。
}
void main() //主函
{
HanShu(); //子函数被调
View(a); //把 a 发送到电脑端的串口助手软件上观察。