Fortran有一种计算效率高的方法,称为“计算转到”。该构造使用分支表中的索引来执行直接转到。如果我没记错的话语法是:
go to index (label1, label2, ...)
索引用于引用括号列表中的代码指针(标签)。
我有一个案例,其中计算的 goto 是比 switch 语句更好的解决方案,我想构建一个,但我不知道如何构建。
现在在 jibes 和 slings 到来之前,编译器可以优化计算的 goto,但我不能保证它会。
始终可以使用 switch 语句。在某些情况下,switch 语句可以优化为跳转表(计算 goto 的实现)。
但是,这只有在 case 值的范围几乎是密集覆盖时才有可能(从低值到高值的范围内几乎每个整数都有一个 case 语句)。如果不是这种情况,则实现可能是二叉树。编译器编写者可以选择是否在适当的时候优化跳转表。在二叉树将始终满足 switch 语句的语义的地方,有时跳转表就足够的地方让我问我是否可以在适当的时候保证跳转表。我无法控制编译器编写器。
作为一个简单的案例,我经常编写词法分析器(FSMs),我使用三种数据结构,一种将输入映射到可接受的字母表,一种执行节点转换,一种基于当前状态和输入值。 FSM 的实现是一个 Mealy machine , 不是 Moore machine ,因此操作是在弧(转移)上执行的,而不是在节点上执行的。
执行的操作通常很小,通常不超过一行源代码。我认识到可以使用函数,并且它们的使用消除了对跳转表的需要。但我相信我不能“指向”内联函数,因此函数是封闭形式的可调用过程。
在大多数情况下,这比带或不带跳转表优化的 switch 语句效率低。如果我可以使用跳转表,那么我就可以避免编译器编写者认为优化的问题,并且能够编写高效的代码。
关于下面提出的与 Fortran 计算的 goto 相关问题的一般情况:这不是对那个/那些评论的批评。但是定性问题,即使它们是真实的,也没有回答问题。
下面有一个使用 void* &&label;
的答案,对此我要感谢你。但是,唉,正如您指出的那样,这是非标准的 C/C++,将来可能不会出现。所以,最好不要这样做。
我希望我已经回答了“获得更好的编译器”的评论。我希望我至少已经解决了使用函数指针的问题。最后,这对我来说是一个好奇的时刻。我不认为我应该提供为什么我认为这个问题具有一定承载力的杀菌历史。但现在我知道了。无论何时,我的意思是无论何时,我都会写信给这个小组,我最好告诉你们我所有的小鸭子是什么,这样它们就可以被正确地击落。
最佳答案
如果你用最近的 GCC 编译编译器(例如,GCC 7 或 GCC 6)——甚至对于 C 代码,旧版本的 GCC,您可以使用它的 labels as values语言扩展(因此在C++11 或C++14 标准之外),它适用于C 和C++。前缀 &&
运算符给出标签的地址,如果后跟 *
间接运算符则计算 goto
.你最好让目标标签开始一些 block 。
例如:
#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;
}
}
(当然,对于上面的代码,普通的 switch
会更好,而且可能同样高效)
顺便说一句,最近Clang (例如, clang++-5.0
)也接受该扩展。
(计算的 goto 对异常不友好,因此它们可能会在未来的 C++ GCC 版本中消失。)
还有threaded code编程技术,您可以使用它编写一些非常有效的(字节码)解释器,并且在那种特定情况下,代码保持非常可读(因为它是非常线性的)并且非常有效。顺便说一句,您可以使用宏和条件编译隐藏此类计算的 goto - 例如,#if
-s-(例如,在不支持该扩展的编译器上使用 switch
);那么您的代码将非常便携。有关 C 中的示例,请查看 OCaml的 runtime/interp.c .
对于 reference Eli Bendersky,计算的 goto 版本更快,原因有二:
- 由于边界检查,switch 每次迭代会多做一些事情。
- 硬件分支预测的影响。
编译器可以实现开关
的多种变体。
- 使用
if
结构对开关空间进行二进制搜索。 - “案例”位置表(计算出类似的 goto)。
- 一个计算分支,要求所有情况都具有相同的代码大小,形成“代码数组”。
对于 OPs 状态机调度,第 2 项是最好的情况。它是唯一不需要返回到主 switch
调度位置的构造。因此,break;
可以将控制转移到下一个case
。这就是该机制对分支预测更有效的原因。
关于c++ - 你能用 C++ 做一个计算 goto 吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45380073/