这两天在上电子发烧友学院的从零开始写RTOS的课程。刚学到任务切换的章节,发现老师给的例子中的task都没有局部变量。于是自己探索了一下有局部变量的task,在做任务切换的时候具体是什么行为,当然其实本质就是在调用函数的时候Keil编译器是怎么做局部变量保存的。
先给出老师告诉我的标准答案:局部变量一般是放在内和寄存器或者当前任务的堆栈里面
-_-|| 拜托能不能说详细点啊喂!
只能自己上手探索了。在老师给的例子上作了简化,便于理解。
先上代码
// called function in task
void TaskSched()
{
delay(100);; // 调用一个其它函数防止这个TsakSched函数被优化掉.
}
// task code
int g_variable;
void task(void * param)
{
int i1=0;
for(;;)
{
i1++;
g_variable=i1; //这里的赋值是保证在调试的时候局部变量不会被优化掉.
TaskSched();
}
}
非常简单的小程序,我们想要观察的就是在task中调用TaskSched函数的时候,i1(我为啥要用i1呢好奇怪...)这个变量被编译器存到了哪里。让我们来看看反汇编的结果。
上面一段是task的反汇编,很明显i1这个局部变量在task运行的时候是一直存放在r2寄存器中。下面这段是TaskSched的反汇编结果,可以看到在这个函数运行之初有一个压栈的操作...ちょっと待って,为什么没有把r2压倒栈里?难道这个意思是函数调用的时候不保存局部变量?我的世界观好像有点崩塌...老师不是这么教我的呀...
仔细想一想,TaskSched这个函数太过简单,整个执行过程只用到了r0寄存器,对r2寄存器并没有任何影响,所以不压栈也是可以理解的。那我把TaskSched改的复杂点。
void TaskSched()
{
int i=100;
int j=200;
int k=200;
delay(i);
delay(j);
delay(k);
}
在TaskSched中加了三个局部变量来增加寄存器的使用,来看反编译结果
这次终于在函数调用的时候是把r4压栈了,为什么不是r2?
因为这次编译task函数的i1变量用的就是r4......
看来这个实验的结果跟老师说的还是一致的,
结论
- 函数运行的时候,局部变量是保存在寄存器中的。
- 调用其他子函数的时候,
a. 如果子函数不复杂,局部变量不会被压栈,这样可以省机器周期和存储空间,还是很好的。
b. 如果子函数比较复杂,局部变量会被压到栈中,等子函数运行完了,再从栈中取出来。
补充
补充内容就不多做解释了,算是实验的附属产物。
局部变量在函数运行的时候是保存在寄存器中的,给局部变量赋初值的过程其实就是给特定的寄存器中填数。如果没有给局部变量赋初值,在使用局部变量的时候编译器还是会从选定的寄存器中取值, 但是这个值是多少就不好说了。所以一定要给局部变量赋初值呀。
虽然上面说了局部变量在函数运行的时候是保存在寄存器中的,但是寄存器就那么几个,要是使用的局部变量太多,还是会被压到栈里面的。所以为了用太多局部变量可能会导致栈溢出。