初步理解c语言栈的运行机理代码段分区
Posted 努力把握好每一天,只愿成为更好的自己
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了初步理解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翻转输出(示波器捕捉),用于确保栈空间的安全性。
以上是关于初步理解c语言栈的运行机理代码段分区的主要内容,如果未能解决你的问题,请参考以下文章