【问题标题】:Is there a way to call a function without adding to the call stack?有没有办法在不添加到调用堆栈的情况下调用函数?
【发布时间】:2025-12-31 19:00:01
【问题描述】:

我有一些 goto-laden C++ 代码,看起来像

#include <stdlib.h>
void test0()
{
    int i = 0;
loop:
    i++;
    if (i > 10) goto done;
    goto loop;
done:
    exit(EXIT_SUCCESS);
}

我想去掉gotos,同时(主要)保留原始代码的外观;或多或少地排除了forwhile 等(除非它们隐藏在宏后面),因为这会过多地改变现有代码的外观。想象一下,想要最小化现有代码和更改代码之间的“差异”。

一个想法是使用带有方法的类:

class test1 final
{
    int i = 0;
    void goto_loop()
    {
        i++;
        if (i > 10) goto_done();
        goto_loop();
    }
    void goto_done()
    {
        exit(EXIT_SUCCESS);
    }
public:
    test1() { goto_loop(); }
};

这可行,但每次调用 goto_loop() 都会添加到调用堆栈中。有什么方法可以进行类似exec 的函数调用吗?也就是说,以某种方式调用“内联”函数...执行附加代码而不添加到调用堆栈?有没有办法使尾递归显式?

使用 C++20(甚至 C++23)是可以接受的,尽管 C++17 解决方案会“不错”。


对于所有想知道“为什么”的人真正的原码是BASIC...

【问题讨论】:

  • 为什么没有whilefor 循环?
  • for (int i = 0; ++i &lt;= 10;) { } 怎么样?
  • 对不起,我不同意这出戏。当您的代码展开到带有两个标签的 goto 时,您的代码看起来与 for 循环非常相似,因此您的带有两个功能的版本是更剧烈的变化恕我直言
  • “但是对goto_loop() 的每次调用都会添加到调用堆栈中。”。没必要,尾调用优化可以申请那种递归。
  • @JackBrown 您已将基于循环的解决方案更改为基于递归的解决方案。在我看来,这比将 goto 方法更改为 forwhile 循环更具侵入性。

标签: c++ recursion goto


【解决方案1】:

听起来你想ensure tail call optimization。目前没有标准的方法来做到这一点,但如果你使用 Clang 你可以use the clang::musttail 属性。

【讨论】:

  • @JackBrown 也许很好,或者你可以使用setjmp,但这比goto 还要糟糕。
【解决方案2】:

循环可能会保留代码结构:

void test2()
{
    int i = 0;
    for (;;) {
        i++;
        if (i > 10) break;
        continue;
    }
    exit(EXIT_SUCCESS);
}

就算

void test3()
{
    for (int i = 0; i < 11; ++i) {
        /*Empty*/
    }
    exit(EXIT_SUCCESS);
}

会更惯用(假设一些有用的代码,因为循环实际上是无用的)。

【讨论】:

    【解决方案3】:

    我的解决方案是编写一个停止递归的exec() 例程:

    template<typename Func>
    void exec(const Func& f)
    {
        using function_t = Func;
        static std::map<const function_t*, size_t> functions;
        const auto it = functions.find(&f);
        if (it == functions.end())
        {
            functions[&f] = 1;
            while (functions[&f] > 0)
            {
                f();
                functions[&f]--;
            }
            functions.erase(&f);
        }
        else
        {
            functions[&f]++;
        }
    }
    

    使用该实用程序,我可以或多或少地保留现有代码的外观

    class test4 final
    {
        int i = 0;
        void goto_loop_() {
            i++;
            if (i > 10) goto_done(); } 
        void goto_loop() { goto_loop_(); static const auto f = [&]() { goto_loop(); }; exec(f); }
        void goto_done()
        {
            exit(EXIT_SUCCESS);
        }
    public:
        test4() { goto_loop(); }
    };
    

    (使用 lambda 避免了指向成员函数的麻烦。)

    【讨论】:

      最近更新 更多