递归调用在单片机中的应用

cpp 复制代码
void InOrder(BinTree root)
{ 
 if (root == NULL)// 递归出口
 return; 
 InOrder(root->lchild); // 自己调用自己,处理左子树 printf("%d ", root->data); 
 InOrder(root- >rchild); // 自己调用自己,处理右子树 
}

递归的两个必备条件,这里全部具备:

  1. 递归终止条件(基线条件)if(root == NULL) return; 当走到空节点时,直接返回,不再继续调用,防止无限循环死递归、栈溢出。
  2. 递推公式(自己调用自己,缩小问题规模) 整棵树的中序遍历 = 左子树中序遍历 + 打印根 + 右子树中序遍历 把 "遍历整棵大树" 拆成 "遍历更小的左、右小树"。

一、根本原理:递归使用的是「系统栈(栈帧)」

递归遍历:

c

运行

复制代码
void InOrder(BinTree root)
{
    if (root == NULL) return;
    InOrder(root->lchild);
    printf("%d ", root->data);
    InOrder(root->rchild);
}

每一次 InOrder() 自调用,单片机都会在全局栈 Stack里分配一块栈帧,保存:

  1. 形参 root 指针
  2. 函数返回地址
  3. 函数内部临时变量(printf 缓存、寄存器备份)

栈的致命限制(单片机特性)

PC 电脑栈通常几 MB; 单片机栈极小 :常见 STM32、8051 默认栈只有 512 字节 / 1KB / 2KB。 栈空间是启动文件预先固定分配,不能动态扩容。

二、二叉树深度决定栈占用,不是节点总数

栈消耗只和树的深度有关,和总节点数量无关:

  1. 平衡完全二叉树:深度很小,比如 1000 个节点深度≈10,递归完全安全;
  2. 单边斜树(最坏场景):所有节点只有左孩子,退化成单链表

plaintext

复制代码
1
/
2
/
3
/
4
...
/
100

此时递归深度 = 100,每一层都压栈,栈帧层层叠加,很快占满栈内存,直接硬崩溃。

举个量化例子(STM32)

假设一次InOrder栈帧占用 20 字节,栈总大小 1KB(1024B): 最大安全递归深度 ≈ 1024 / 20 = 50 层。 如果二叉树深度超过 50,直接栈溢出:程序跑飞、死机、HardFault 硬件错误。

三、单片机中两种解决方案(工程必用)

方案 1:改用非递归遍历(手动堆模拟栈,推荐量产使用)

自己用数组 / 堆内存手动实现栈,不占用系统栈,深度随便多大都不怕。 中序非递归代码(单片机可直接用):

c

运行

复制代码
#define MAX_TREE_DEPTH 200
void InOrder_NoRecursion(BinTree root)
{
    BinTreeNode* stack[MAX_TREE_DEPTH]; // 手动栈,放全局/堆,不占函数栈
    int top = 0;
    BinTreeNode* cur = root;

    while (cur != NULL || top > 0)
    {
        // 一路往左压节点
        while (cur != NULL)
        {
            stack[top++] = cur;
            cur = cur->lchild;
        }
        // 弹出访问
        cur = stack[--top];
        printf("%d ", cur->data);
        // 切换右子树
        cur = cur->rchild;
    }
}

优势:

  • stack数组定义为全局变量,存在.bss 段,完全不消耗系统栈
  • 容量自己定义 200/500,深度再大也不会溢出;
  • 无递归调用,无栈帧叠加,嵌入式稳定。

方案 2:加大单片机栈容量(临时折中,不推荐长期)

  1. STM32:修改启动文件 startup_stm32xxxx.s

asm

复制代码
Stack_Size      EQU     0x0400  ; 原0x200(512字节)改成1024字节
  1. Keil C51 8 位单片机:修改栈大小编译选项 缺陷:
  • RAM 资源宝贵,加大栈会挤占全局变量、缓冲区内存;
  • 遇到深度极大的斜树,依然有溢出风险,治标不治本。

四、补充单片机开发规范

  1. 深度不确定的树、链表、递归逻辑,一律禁止用递归
  2. 递归只允许用于固定极小深度场景(深度 < 10);
  3. 所有深度不可控的遍历(二叉树、深度优先搜索 DFS),强制手动栈 / 队列迭代实现;
  4. 栈溢出故障极难调试:无报错、随机死机、硬件异常中断。

极简总结

  1. 递归二叉树遍历深度越大,栈占用越高
  2. 单边链式二叉树会让递归深度等于节点数,单片机小栈必然溢出;
  3. 单片机正式项目必须替换为非递归迭代写法,用全局数组手动模拟栈,规避栈溢出。