产生深层调用堆栈的代码可以被视为反模式吗?

Can code that produces deep call stacks be considered as an anti-pattern?

我们正在围绕 LLVM 库进行研究,我们发现 IR 库有时会达到调用堆栈多达 29 个方法调用。

有时,当我在 iOS 框架中看到一些崩溃时,我也会观察到相当深的调用堆栈。

我的问题是,我们是否可以推断在如此大的深度调用自身的代码设计是否存在问题。

这是一个例子:

/usr/local/LLVM/llvm/unittests/IR/AttributesTest.cpp:54
  /usr/local/LLVM/llvm/lib/IR/LLVMContext.cpp:162
    /usr/local/LLVM/llvm/lib/IR/LLVMContext.cpp:162
      /usr/local/LLVM/llvm/lib/IR/LLVMContextImpl.cpp:54
        /usr/local/LLVM/llvm/lib/IR/LLVMContextImpl.cpp:59
          /usr/local/LLVM/llvm/lib/IR/Module.cpp:60
            /usr/local/LLVM/llvm/lib/IR/Module.cpp:62
              /usr/local/LLVM/llvm/lib/IR/Module.cpp:456
                /usr/local/LLVM/llvm/lib/IR/Function.cpp:350
                  /usr/local/LLVM/llvm/lib/IR/BasicBlock.cpp:98
                    /usr/local/LLVM/llvm/include/llvm/ADT/ilist.h:282
                      /usr/local/LLVM/llvm/include/llvm/ADT/ilist.h:267
                        /usr/local/LLVM/llvm/lib/IR/SymbolTableListTraitsImpl.h:76
                          /usr/local/LLVM/llvm/lib/IR/BasicBlock.cpp:90
                            /usr/local/LLVM/llvm/lib/IR/SymbolTableListTraitsImpl.h:58
                              /usr/local/LLVM/llvm/lib/IR/ValueSymbolTable.cpp:75
                                /usr/local/LLVM/llvm/lib/IR/ValueSymbolTable.cpp:47
                                  /usr/local/LLVM/llvm/include/llvm/Support/Casting.h:132
                                    /usr/local/LLVM/llvm/include/llvm/Support/Casting.h:112
                                      /usr/local/LLVM/llvm/include/llvm/Support/Casting.h:122
                                        /usr/local/LLVM/llvm/include/llvm/Support/Casting.h:96
                                          /usr/local/LLVM/llvm/include/llvm/IR/Value.h:777
                                            /usr/local/LLVM/llvm/include/llvm/Support/Casting.h:132
                                              /usr/local/LLVM/llvm/include/llvm/Support/Casting.h:122
                                                /usr/local/LLVM/llvm/include/llvm/Support/Casting.h:75
                                                  /usr/local/LLVM/llvm/include/llvm/IR/Value.h:771
                                                    /usr/local/LLVM/llvm/include/llvm/Support/Casting.h:132
                                                      /usr/local/LLVM/llvm/include/llvm/Support/Casting.h:122
                                                        /usr/local/LLVM/llvm/include/llvm/Support/Casting.h:75
                                                          /usr/local/LLVM/llvm/include/llvm/IR/Value.h:759

P.S。示例调用堆栈实际上是由 LLVMContext class: LLVMContext::~LLVMContext() 的析构函数生成的。这是来自 Java 世界的一个非常古老的 post 的另一个例子:Java call stack – from HTTP upto JDBC as a picture.

My question is whether we can reason about if there might be something wrong with a design of a code that calls itself at so big level of depth.

我要冒险说 "yes",但是你的问题和那个答案都有问题。

关于是否可能 的 理由的概念并不值得一提。您可以推断循环是否终止;你可以证明它有或没有。您可以推断是否存在竞争条件。您无法推断某些东西是否 可能 存在。

没有标准,没有度量标准,没有权威会告诉你调用堆栈有多深。请记住,几乎 any 调用堆栈是可以避免的:调用堆栈是库的 "factorization"(如果你愿意的话)的产物。可以想象用宏或 C++ 模板替换函数调用。逻辑效果相同,指令数略少。可能更便宜,因为内联,或者更昂贵,因为重复的代码。但至少堆栈指针没有改变!

所以我将您的问题解释为:相对于已实现的功能,调用堆栈是否很大,是否有理由仔细检查代码是否存在不必要的复杂性? 对此,我说是的。

面对您所描述的情况,我想知道其中一些调用是否是 "manager" 函数:某种通用的包装器,但作用不大。我记得几年前读过一个编组库,其中 write(2) 的调用堆栈深度为 14。除了将数据洗牌到另一个抽象之外,大部分代码没有做任何事情。

并非巧合:该库和您的库都是 C++。 C++ 使得隐式函数调用变得容易,析构函数就是一个例子。如果您将该析构函数编写为 C 函数,我敢打赌它会又长又扁。析构函数也很容易在释放内存之前做很多 "cleaning up" ;在 C 中,您可能只调用了 free(3) 几次,然后就完成了。

所以调用堆栈深度本身并不是真正的问题。但 IMO 你的直觉是正确的:大量调用堆栈覆盖少量功能表明一种超级有组织的意大利面条代码。重新审视功能肯定不会有什么坏处,或许可以寻找减少抽象数量的方法。

通常,是的,当我看到深层调用堆栈时,我会产生怀疑,原因与 James K. Lowden 类似。

但是你的例子是例外。看来您的代码正在遍历源代码的表示形式。源代码包含深层嵌套结构,您的代码递归地遍历这些结构。因此,调用堆栈将随着代码嵌套的深度而增长。这完全符合预期并且很好,但我希望您有大量内存分配给堆栈。