其他分享
首页 > 其他分享> > 初步理解c语言栈的运行机理、代码段分区

初步理解c语言栈的运行机理、代码段分区

作者:互联网

栈区:

  栈(stack):是一种先进后出的内存结构,所有的局部变量,函数的形参都是由编译器自动放出栈中,当一个自动变量超出其作用域时,自动从栈中弹出。出入栈是由C语言编译器自动分配释放。

  栈大小(stacksize):通常可以配置编译器或通过改写链接文件调整栈空间大小。

  栈溢出:当栈空间已满,还继续往栈内压变量,会导致栈溢出,通常表现为程序非预期运行(部分RAM数据乱掉,严重情况下会导致程序跑飞)。

 

基于飞思卡尔9S12系列 16bit MCU理解栈的运行机理

1、建立以下示例代码片上仿真工程(9S12D64为例说明)

 1 #include <hidef.h>      /* common defines and macros */
 2 #include "derivative.h"      /* derivative-specific definitions */
 3 
 4 static int StaticTestNotInit[10]; /* 未初始化的静态数组 */
 5 int TestNotInit[10];  /* 未初始化的全局变量数组 */
 6 int Test[] = {0x10,0x20}; /* 赋初值的全局变量数组 */
 7 
 8 int TestFunction(int a, int b) {
 9   static int TempVar[10]; /* 子函数静态数组 */
10   static int Index = 0;
11   
12   int TestTempList[8] = {0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80};  /* 子函数局部变量数组 */
13   
14   int ret = a + b + TestTempList[Index];
15   TempVar[Index++] = ret;
16   if(Index >= (sizeof(TempVar)/sizeof(int))) Index = 0;
17   
18   return ret;
19 }
20 
21 void main(void) {
22   /* put your own code here */
23   int i = 0;  /* 主函数临时变量 */
24   for(i = 0; i < sizeof(StaticTestNotInit)/sizeof(int);i++)
25     StaticTestNotInit[i] = 0x01;
26     
27   for(i = 0; i < sizeof(TestNotInit)/sizeof(int);i++)
28     TestNotInit[i] = 0x02;
29     
30   for(i = 0; i < sizeof(Test)/sizeof(int);i++)
31     Test[i] = 0x03;
32     
33   i = TestFunction(Test[0],Test[1]);
34   i = TestFunction(TestNotInit[0],StaticTestNotInit[1]);
35 
36     EnableInterrupts;
37 
38 
39   for(;;) {
40     _FEED_COP(); /* feeds the dog */
41   } /* loop forever */
42   /* please make sure that you never leave main */
43 }

2、编译后打开map文件:

2.1 栈区ram划分

 

栈大小(stacksize),工程默认为0x100,地址从0x400-0x4FF,可在链接文件prm中修改,如下图所示,修改为0x200后再次编译查看map文件。

 

 

 

 

 

 栈空间调整为0x200,地址范围为:0x400-0x5FF

2.2 不同位置定义的变量内存分配

 

 

 

 

 如上图所示:

第4行定义的未初始化的静态数组 StaticTestNotInit[10] ,分配在.bss数据段,地址空间为0x606-0x619,共20字节(16位机的int宽度为2字节)

第5行定义的未初始化全局变量数组 TestNotInit[10] ,分配在.common数据段,地址空间为0x62E-0x641,同样为20字节

第6行定义的赋初值的全局变量数组 Test[2] ,分配在.data数据段,地址空间为0x600-0x603,共4字节

第9行、10行定义的子函数静态变量TempVar[10],Index,分配在.bss数据段,地址空间分别为0x61A-0x62D和0x604-0x605;

其他变量(main函数内的i,TestFaunction函数内部的TestTempList[8],ret等),均在运行时分配在栈空间内。

3、仿真调试

3.1 mian函数运行开始时的memory分布

  如下图所示,MCU上电运行startup函数,调用main函数接口时,将startup函数的下一条指令压栈,即栈区的0x5FD-0x5FF地址空间(0x00C00B,特别说明:9S12系列单片机全局地址为24bit)。

.data段数据直接被startup函数填充为对应初值,.bss及.common数据段则被startup函数填充为0

(startup函数的实现,此处暂不讨论,通常应用也无需关注,当需要做bootloader时再进行深入探究)

 

 

   单步运行至24行代码时,此时第23行定义的局部变量i被分配到栈空间,并被附初值0

 

 

   继续运行至27行代码,可以看到StaticTestNotInit数组所有值均被更改为0x01

 

 

    继续运行至30行代码,可以看到TestNotInit数组所有值均被更改为0x02

 

 

     继续运行至33行代码,可以看到Test数组所有值均被更改为0x03

 

  进入子函数,此时先将形参a压入栈空间(0x5F9-0x5FA),主函数的下一条指令地址(0x3C809C)被压入栈空间(0x5F6-0x5F8)

 

 

   单步运行,此时可以看到,先为局部变量数组TestTempList[8]以及ret分配了栈空间,然后在将第二个形参压入栈空间(0x5E2-0x5E3),此处压栈顺序与编译器有关

 

   继续单步,可以看到初步对TestTempList赋初值,然后计算出ret的结果

 

   当子函数执行完毕后,返回主函数时,子函数分配的栈空间自动回收

 

 

基于瑞萨RH850系列 32bit MCU理解栈的运行机理(暂略,后期补充)

 

 

总结

综上所述,子函数调用会将主调函数的下一条指令地址、子函数形参压栈,并在栈空间中为子函数分配所需的所有局部变量(按作用域进行分配和回收)。

所以,工程应用时,可根据map文件中的函数调用关系,查看各个调用链级的局部变量、形参数量进行STACKSIZE预估,并按照1.5-2倍进行空间预留,才能有效保证栈空间的安全性

PS:函数指针部分编译器无法绘制调用关系,并且MCU运行工况的高复杂性,中断调用等均需消耗栈空间,所以为了保证安全性,需进行空间预留

栈溢出判断:可在项目进行性能调优阶段,增加栈空间监测,采用高精度定时器,实时判断栈空间80-90%位置的数据是否被改变,通过IO翻转输出(示波器捕捉),用于确保栈空间的安全性。

 

标签:10,函数,int,分区,代码段,子函数,数组,空间,机理
来源: https://www.cnblogs.com/MrZhang1700/p/14827994.html