使用虚函数调用时节省堆栈空间

conserve stack space when using virtual function calls

本文关键字:节省 堆栈 空间 函数调用      更新时间:2023-10-16

我目前正在为STM32µC开发c++。基本上,我想为I²c添加一个抽象层,以便拥有虚拟的I²c总线,它实际上位于物理I²c总线上的多路复用器后面。

我们已经有了一个将驱动程序和多路复用逻辑结合起来的驱动程序,在OOP部门明显缺乏…因此,我在I²C访问和虚拟总线实现中添加了间接级别,以将驱动程序与多路复用器逻辑解耦并隐藏全局知识:

Device -> Virtual I²C -> I²C Multiplexer -> Abstracted I²C -> Low-Level I²C

现在我的问题基本上是,每个接口都必须定义一个传输函数

virtual
transmit(const uint8_t address,
         uint8_t *tx,
         size_t lentx,
         const uint8_t *rx,
         const size_t lenrx,
         const size_t timeout
         );

只传递一些参数。通常:

virtual
transmit(...) {
    this->driver->transmit(this->address, ...);
}
然而,事实证明,每个虚函数调用会给我的堆栈需求增加40字节,因为它们:
  1. 获取栈上的参数
  2. 为函数调用分配堆栈
  3. 将输入复制到堆栈新分配部分的正确位置
  4. 执行函数调用
  5. 将输出复制回堆栈中传入参数的正确位置

现在,我们只是最近才改用c++,所以我对使用c++进行嵌入式系统开发的微调还不是很了解。然而,对我来说很奇怪的是,已经有三个间接层了,基本上已经耗尽了堆栈空间……例如,我以这种方式单独遍历虚拟调用会损失164字节:设备到虚拟总线,虚拟总线到I²C多路复用器,I²C多路复用器到抽象I²C,真正的调用发生在那里。

理想的解决方案是显而易见的。显然,虚函数调用应该只为它需要传递的额外变量分配尽可能多的空间,并重新排序参数:

Incoming: tx*, len, rx*, len, to
Allocate: tx*, len, rx*, len, to, ___
Reorder : ___, tx*, len, rx*, len, to
Add args: sla, tx*, len, rx*, len, to
     ---------- Call ----------
Reorder : tx*, len, rx*, len, to, ___
Dealloc : tx*, len, rx*, len, to
Output  : tx*, len, rx*, len, to
所以真正的问题是:我能强迫GCC注意到这种传递风格的行为,并强迫它尽可能多地保留堆栈空间吗?

虽然您不能说服gcc自动更改堆栈的布局,但您当然可以通过定义单个struct来手动更改堆栈布局,并在其上设置参数,并传递指向该struct的共享指针。

你可以这样做:

struct TxRxParams {
    uint8_t *tx;
    size_t lentx;
    const uint8_t *rx;
    const size_t lenrx;
};

现在您可以分配一次TxRxParams,并将指向它的指针传递给各种函数,无论虚拟还是非虚拟,以节省堆栈空间