你能用C ++

时间:2017-07-28 18:23:35

标签: c++ c++11

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

go to index (lable1, lable2, ...)

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

我有一种情况,计算goto是一个比switch语句更好的解决方案,并且想构建一个但不能弄清楚如何。

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

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

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

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

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

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

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

Thanx everyone。

3 个答案:

答案 0 :(得分:10)

如果您使用最近的 GCC编译器(例如GCC 7或GCC 6)进行编译 - 或者对于C代码,使用旧版本的GCC - 您可以使用其 labels as values语言扩展(因此在C ++ 11或C ++ 14标准之外),适用于C& 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;
    }
}    

(当然,对于上面的代码,普通的switch会更好,而且效率可能更高)

BTW,最近Clang(例如clang++-5.0)也接受该扩展名。

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

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

答案 1 :(得分:4)

是(不是直接),使用switch或创建函数指针或函数对象的表。

大多数编译器会将switch转换为计算出的goto(a.k.a.跳转表)。

函数指针数组大致相同。您取消引用数组槽以执行该功能。

您也可以使用std::map或其他具有函数指针的容器。

编辑1:使用数组计算goto的示例

typedef (void) (*Pointer_To_Function)();
void Hello()
{
  cout << "Hello\n";
}

void Bye()
{
  cout << "Bye\n";
}

static const Pointer_To_Function function_table[] =
{
  Hello,
  Bye,
}

int main()
{
  (*function_table[0])();
  (*function_table[1])();

  // A "computed goto" based on a variable
  unsigned int i = 0;
  (*function_table[i])();
  return 0;
}

编辑2:使用switch计算转到

int main()
{
  int i = 1;
  switch (i)
  {
    case 0: Hello(); break;
    case 1: Bye(); break;
  }
  return 0;
}

告诉编译器为上面的每个例子生成一个汇编语言列表。

最有可能的是,它们看起来像计算的goto表。如果没有,请提高优化级别。

为了实现switch或数组的良好优化,大小写值应该在一个范围内是连续的。对于选择带孔的范围,std::map可能更有效(或使用较小数量的表)。

答案 2 :(得分:2)

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]();
}

测试代码:

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

输出101。

Live example

如果您需要大量的空白,则必须进行额外的工作。短缺&#34;差距&#34;可以用无效的跳转目标来处理:

using action = void();
static action*const invalid_jump = 0;

如果实际调用它应该是段错误。

对于一个非常稀疏的表,你想要为每个目标的表大小和编译时间索引传递编译时常量,然后从中构建表。根据您希望的效率,可能需要合理的编译时编程。