Nuvoton Keil技巧篇4 -- 堆和栈

日期2025-08-22

        攻城狮们在进行软件编写时,经常会碰到莫名其妙的宕机了,或者出现Hardfault报错,查了半天代码也没有找到原因,最后才发现是栈溢出了。我这边整理了一些关于堆和栈的一些个人见解,希望能在代码设计的时候能避免踩到到这些大坑,如有见解错误的地方也欢迎指正。
        RAM空间:分成.data、.bss、堆heap和栈stack。

 

 

       .data 段: 初始化的全局/静态变量

       .bss 段: 未初始化的全局/静态变量

       堆(heap):malloc()函数动态分配的内存就属于堆的空间,运行时从 RAM低地址向上增长。在单片机启动文件里也有对堆大小(Heap Size)的定义。
       比如新唐M031例程startup文件里面Heap Size设置为0,需要使用的时候自行设置大小。

       栈(stack):存放函数局部变量、函数形参、函数返回地址、中断上下文地址(中断嵌套从内部开始计算)等,从指定RAM区域的高地址向下增长。所以我们在函数的局部变量、数组这些不能超过栈大小(含嵌套的函数),否则程序就会崩溃进入HardFault‌。

       当一个函数被调用时,系统会在栈上为该函数分配一块连续的空间,用来存放这些临时变量,当函数执行完毕,这块空间会自动释放。栈的内存分配和释放是由编译器自动完成的,遵循后进先出(LIFO)的原则。每当有新的函数调用发生时,栈指针会向下移动,为新的局部变量和参数分配空间;函数返回时,栈指针会向上移动,回收之前分配的空间。

        比如新唐M031例程startup文件里面Stack Size设置为0x200,需要使用更大的栈空间可以自行修改。

 

 

        栈溢出是导致系统崩溃、死机、行为异常甚至安全漏洞的常见原因。栈空间是有限的,当函数调用嵌套过深、局部变量过大、中断嵌套过多等情况消耗的栈空间超过了预留的大小,就会发生栈溢出,覆盖其他内存区域(如堆、全局数据区甚至代码区)。

 

        导致栈溢出的具体操作和场景:

        1、过深的递归调用

              每次递归调用都会在栈上压入新的返回地址、参数、局部变量等。如果递归层数太多,即使每层的局部变量很小,累积起来也会耗尽栈空间。

        2、定义过大的局部变量(尤其是数组)

              在函数内部声明非常大的局部变量(如大数组、大结构体),比如定义一个 char buffer[4096]; 就会在函数入口时在栈上分配 4KB 空间。如果栈总大小只有 1KB 或 2KB,瞬间就会溢出。

        3、过深或过长的函数嵌套:

              函数 A 调用 B, B 调用 C, C 调用 D... 。每个函数调用都会在栈上压入返回地址、寄存器上下文(由编译器决定)、参数(有时通过寄存器传递,但复杂或很多参数时也会用栈)、以及它自己的局部变量。调用链越长,累积的栈消耗越大。

        4、中断嵌套过深或中断处理函数消耗过大

              允许高优先级中断打断低优先级中断(中断嵌套),在中断服务程序中执行耗时操作、定义大局部变量、调用复杂的函数(特别是调用链长或有大局部变量的函数)。

        5、传递大型结构体或数组作为函数参数

               在函数参数使用非常大的局部变量(如大数组、大结构体),栈会需要分配给这个局部变量很大的空间,很容易超出栈总大小。

★方案内容均由个人提供,与平台无关,如有违法或侵权,请联系大大芯方案专属信箱