过深的 C++ 类层次结构会导致堆栈溢出吗?

Posted

技术标签:

【中文标题】过深的 C++ 类层次结构会导致堆栈溢出吗?【英文标题】:Can an excessively deep C++ class hierarchy cause a stack overflow? 【发布时间】:2012-02-13 11:04:36 【问题描述】:

假设我有一个继承过深的 C++ 程序,如下所示:

using namespace std;

class AbstractParentGeneration0 
    private:
      ...
    protected:
      ...
    public:
      virtual returnVal funcName(void) = 0;
;

class AbstractParentGeneration1: virtual public AbstractParentGeneration0 
    private:
      ...
    protected:
      ...
    public:
      virtual returnVal funcName(void) = 0;
;

.
.
.

class AbstractParentGeneration999999999: virtual public AbstractParentGeneration999999998 
    private:
      ...
    protected:
      ...
    public:
      virtual returnVal funcName(void) = 0;
;

class ChildGeneration: public AbstractParentGeneration999999999 
    private:
      ...
    protected:
      ...
    public:
      returnVal funcName(void)  ... ;
;

假设程序的性质是深度继承 不能被压实(假设它代表一个进化物种 谱系或深层分类层次结构)

调用顶层抽象类时不会有堆栈溢出的危险吗?

什么策略(除了“ulimit -s bytes”或折叠 抽象层次结构)C++ 程序员使用在其中工作吗? 系统边界?

有没有办法将深层垂直继承层次结构展平? 许多主机系统通过 RPC?

有些人会设计自己的调用堆栈机制吗?

有分布式网络/集群调用栈这种东西吗?

【问题讨论】:

+1 询问 ***s 去掉继承的“虚拟”方面,并且无论它下面有多少个基类,类的 sizeof() 都不会改变(忽略可能存在的任何用户定义的数据成员) . 为什么不试试看呢? 【参考方案1】:

这很可能会违反编译器的某些内部约束。如果出现问题,您将看不到运行时。

解决这个问题的一般方法是停止生成这样的代码。不要将此类数据烘焙到运行时中。相反,这应该是某种文件格式,它被加载并解析到类实例的层次结构中,而不是类本身的层次结构中。

【讨论】:

【参考方案2】:

调用顶层抽象类时不会有堆栈溢出的危险吗?

当然有。每个构造函数和析构函数都会在父类中调用它的对应对象,一直到层次结构,生成一个非常深的调用堆栈。

如果它在构造过程中幸存下来,那么调用虚函数就可以了 - 这只是对最终覆盖的单个函数调用。

C++ 程序员使用什么策略(除了 ulimit -a 或折叠抽象层次结构)在系统边界内工作?

就个人而言,我倾向于完全避免继承(除了抽象接口,以及我懒得封装事物的情况)。

在这种情况下,我可能会建议对象的运行时层次结构,也许使用函数指针来提供与虚函数类似的行为;唯一的限制是存储它们所需的内存。它不像使用类型系统来表示您的类别那样优雅,但不太可能遇到实现限制。

【讨论】:

编译器也有其局限性。我怀疑足以在运行时导致堆栈问题的类层次结构会超过编译器的限制。 (当然,从设​​计的角度来看:我可以设想一些情况,其中 4 或 5 深是合理的。不仅如此:仅在机器生成代码中。)【参考方案3】:

为了回答您的具体问题,在调用顶层抽象虚拟方法时,运行时不会有堆栈溢出的危险。 C++ 的大多数实现都会在对象实例中有一个 vtable 指针条目,它直接指向相应的实现函数。

如果您对它的工作原理感到好奇,我建议您编写这样一个具有几个层次结构的程序,并加载一个汇编级调试器来显示实际情况。

【讨论】:

有没有人实现 B+Tree 调度作为调度表的替代方案(以防调度表变得大于可用内存)?有些已经实现了二叉树调度,但这仍然是内存绑定的。 B+Trees 的优势在于它们重叠存储/内存并使用两全其美(内存中的快速查找和巨大的大小但存储的查找速度慢)。 我无法想象您的 dispatch table 会大到无法保存在内存中的情况。您将没有任何空间用于实际工作!【参考方案4】:

尝试一下,自己看看;)

template <int i>
class oferflowMe: public overflowMe<i-1>
    ....
;

template <>
class oferflowMe<1>
    ....
;

【讨论】:

有一个实现定义的模板实例化限制,通常要低很多。 C++03 建议“至少 17”! @MSalters 我见过人们使用模板进行静态计算,例如将 1 到 100 的数字相加。它似乎工作得很好。【参考方案5】:

在这种简单的情况下不是。但是,如果您有多个虚拟继承,那么一些编译器将使用“调整器 thunk”来修复 this。这些小函数最终会出现在您的调用堆栈中。

【讨论】:

编译器会使用这些thunk,但它们会留在堆栈上吗?我认为它的工作方式有点像尾调用 - 函数被调用,并在调用它的最后一个函数时从堆栈中消失。无论如何,这些 thunk 甚至是适当的功能吗?

以上是关于过深的 C++ 类层次结构会导致堆栈溢出吗?的主要内容,如果未能解决你的问题,请参考以下文章

堆栈缓冲区溢出会导致堆损坏吗?

在 iOS 上,presentViewController 会导致溢出(如堆栈溢出)吗?

UIKit pushViewController:animated: 上的调用会导致最终的堆栈溢出(或其他异常)吗?

如何在 C++ 中处理或避免堆栈溢出

如何在 c / c++ 程序中检测可能/潜在的堆栈溢出问题?

.NET 异常处理程序导致 Visual C++ 6.0 异常的堆栈溢出