你能用C++编写一个计算goto吗?

Art*_*rez 4 c++ c++11

Fortran具有计算效率,称为"计算goto".该构造使用分支表中的索引来执行直接转到.如果我没记错的语法是:

go to index (label1, label2, ...)
Run Code Online (Sandbox Code Playgroud)

其中索引用于引用括号列表中的代码指针(标签).

我有一个案例,计算goto是一个比switch语句更好的解决方案,并希望构建一个,但无法弄清楚如何.

现在在jibes和slings到来之前,编译器可以优化计算的goto,但我不能保证它会.


始终可以使用switch语句.在某些情况下,可以将switch语句优化为跳转表(计算goto的实现).但是,这只有在案例值的范围几乎是密集覆盖时才有可能(在低值到高值的范围内,每个整数几乎都有一个case语句).如果不是这种情况,则实现可能是二叉树.编译器编写器可以选择在适当的时候优化跳转表.在二叉树总是满足switch语句的语义的情况下,有时跳转表就足够了,让我问一下我是否可以在适当的时候保证跳转表.我无法控制编译器编写器.

作为一个简单的例子,我经常写词法分析器(FSM),我使用三个数据结构,一个用于将输入映射到可接受的字母表,一个用于执行节点转换,一个用于根据当前状态和输入执行一些代码值.FSM的实现是Mealy机器,而不是Moore机器,因此动作是在弧(转换)上执行而不是在节点上执行.

执行的操作通常很小,通常不超过一行源代码.我认识到可以使用函数,并且它们的使用消除了对跳转表的需要.但我相信我不能"指向"内联函数,因此,函数是从可调用程序关闭的.在大多数情况下,这比使用跳转表优化的switch语句效率低.如果我可以使用跳转表,那么我就避免了编译器编写者对优化的看法以及能够编写高效代码的问题.

关于与Fortran计算goto相关的问题,下面提到的一般情况.这不是对那些评论的批评.但质量问题,即使它们是真的,也没有回答这个问题.

下面有一个答案void* &&label;,我要感谢你.但是,唉,正如您所指出的那样,这是非标准的C/C++,很可能在将来不存在.所以,最好不要这样做.

我希望我已经回答了"获得更好的编译器"的评论.我希望我至少解决了使用函数指针的问题.最后,这对我来说是一个好奇的时刻.我不认为我应该提供我为什么认为这个问题具有一定的携带能力的杀菌史.但现在我知道了.无论什么时候,我的意思是什么时候,我写信给这个小组,我最好告诉你我所有的鸭子是什么,这样他们就可以被击落.

大家好.

Bas*_*tch 11

如果使用最近的GCC编译器(例如GCC 7或GCC 6)进行编译 - 或者对于C代码,使用旧版本的GCC - 您可以将其标签用作值语言扩展(因此 C++ 11或C++之外) 14个标准),适用于C&C++.前缀&&运算符给出标签的地址,goto如果后跟*间接运算符则计算.你最好让目标标签开始一些块.

例如:

#include <map>

int foo (std::map<int,int>& m, int d, int x) {
    static const void* array[] = {&&lab1, &&lab2, &&lab3 };
    goto *array[d%3];
lab1: {
        m[d]= 1;
        return 0;
    };
lab2: {
        m[2*d]=x;
        return 1;
    }
lab3: {
    m[d+1]= 4*x;
    return 2;
    }
}    
Run Code Online (Sandbox Code Playgroud)

(当然,对于上面的代码,平原switch 会更好,并且可能效率很高)

最近,Clang(例如clang++-5.0)也接受了这一扩展.

(计算的gotos不是异常友好的,所以它们可能会在未来的GCC for C++版本中消失)

使用线程代码编程技术,您可以使用它编写一些非常高效的(字节码)解释器,并且在特定情况下代码保持非常可读(因为它非常线性)并且非常有效.顺便说一下,你可以用宏和条件编译-eg #if-s- 隐藏这样的计算得到的结果(例如,switch在不支持该扩展的编译器上使用); 那么你的代码将非常便携.有关C语言的示例,请查看Ocaml的byterun/interp.c

  • @Slava:你是什么意思?这显然是一种语言扩展. (2认同)
  • @Slava,事实上,计算的 goto 可读性与 switch 相同。而且由于编译器通常不会太积极地用计算的 goto 替换 switch,因此我发现这个问题是有效的,至少对于分析目的来说是这样。 (2认同)
  • 我认为自从德古拉(Dracula)还是个小伙子以来,海湾合作委员会(gcc)就有了这种扩展。(很确定它是在95年左右出现的。) (2认同)

Yak*_*ont 5

using jump_func_t = void(*)(void const*);
template<class F>
jump_func_t jump_func() {
    return [](void const*ptr){ (*static_cast<F const*>(ptr))(); };
}
template<class...Fs>
void jump_table( std::size_t i, Fs const&...fs ) {
  struct entry {
    jump_func_t f;
    void const* data;
    void operator()()const { f(data); }
  };
  const entry table[] = {
    {jump_func<Fs>(), std::addressof(fs)}...
  };
  table[i]();
}
Run Code Online (Sandbox Code Playgroud)

测试代码:

int x = 0, y = 0, z = 0;
jump_table( 3,
    [&]{ ++x; },
    [&]{ ++y; },
    [&]{ ++z; },
    [&]{ ++x; ++z; }
);
std::cout << x << y << z << "\n";
Run Code Online (Sandbox Code Playgroud)

输出 101。

实例

如果你想要大量的间隙,就必须做额外的工作。短的“间隙”可以用无效的跳转目标来处理:

using action = void();
static action*const invalid_jump = 0;
Run Code Online (Sandbox Code Playgroud)

如果实际调用的话应该出现分段错误

对于真正稀疏的表,您需要传入表大小的编译时常量和每个目标的编译时索引,然后从中构建表。根据您想要的效率,这可能需要相当花哨的编译时编程。