Keil中函数调用时对局部变量的处理

这两天在上电子发烧友学院的从零开始写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呢好奇怪...)这个变量被编译器存到了哪里。让我们来看看反汇编的结果。

Figure 1. Disassembly of task
Figure 2. Disassembly of TaskSched

上面一段是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中加了三个局部变量来增加寄存器的使用,来看反编译结果

Figure 3. Disassembly of Complex TaskSched

这次终于在函数调用的时候是把r4压栈了,为什么不是r2?

Figure 4. Disassembly of task

因为这次编译task函数的i1变量用的就是r4......
看来这个实验的结果跟老师说的还是一致的,

结论


  1. 函数运行的时候,局部变量是保存在寄存器中的。
  2. 调用其他子函数的时候,
    a. 如果子函数不复杂,局部变量不会被压栈,这样可以省机器周期和存储空间,还是很好的。
    b. 如果子函数比较复杂,局部变量会被压到栈中,等子函数运行完了,再从栈中取出来。

补充


补充内容就不多做解释了,算是实验的附属产物。

  1. 局部变量在函数运行的时候是保存在寄存器中的,给局部变量赋初值的过程其实就是给特定的寄存器中填数。如果没有给局部变量赋初值,在使用局部变量的时候编译器还是会从选定的寄存器中取值, 但是这个值是多少就不好说了。所以一定要给局部变量赋初值呀。

  2. 虽然上面说了局部变量在函数运行的时候是保存在寄存器中的,但是寄存器就那么几个,要是使用的局部变量太多,还是会被压到栈里面的。所以为了用太多局部变量可能会导致栈溢出。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容