c切换和跳转表

时间:2013-06-12 09:25:01

标签: c++ switch-statement jump-table

据我所知,c / c ++中的switch语句有时会编译为跳转表。 我的问题是,是否有任何拇指规则可以确保?

在我的情况下,我做这样的事情:

enum myenum{
MY_CASE0= 0,
MY_CASE0= 1, 
.
.
.
};

switch(foo)
{
  case MY_CASE0:
  //do stuff
  break;
  case MY_CASE1:
  //do stuff
  break;
 .
 .
 .
}

我按顺序覆盖从1到n的所有情况。可以安全地假设它会编译成跳转表吗? 原始代码是一个冗长而凌乱的if else语句,所以至少我获得了一些可读性。

2 个答案:

答案 0 :(得分:12)

一个好的编译器可以并且将在跳转表,链接的if / else或组合之间进行选择。设计糟糕的编译器可能无法做出这样的选择 - 甚至可能会为开关块产生非常糟糕的代码。但任何体面的编译器都应该为switch-blocks生成有效的代码。 Ť

这里的主要决策因素是编译器可以选择if / else,当数字相距很远时[并且不是简单地(例如除以2,4,8,16,256等)变为更接近的值],例如,

 switch(x)
 {
    case 1:
     ...
    case 4912:
     ...
    case 11211:
     ...
    case 19102:
     ...
 }

需要至少19102 * 2字节的跳转表。

另一方面,如果数字靠得很近,编译器通常会使用跳转表。

即使它是if/else类型的设计,它通常会进行“二分搜索” - 如果我们采用上面的例子:

 if (x <= 4912)
 {
     if (x == 1)
     {
        ....
     }
     else if (x == 4912)
     {
         .... 
     }
 } else {
     if (x == 11211)
     {
         ....
     }
     else if (x == 19102)
     {
         ...
     }
 }

如果我们有很多案例,这种方法会陷入很深的境界,人类可能会在三到四个深度之后迷失(请记住,每一个如果从该范围的MIDDLE中的某个点开始),它减少了log2(n)的测试次数,其中n是选择的数量。它肯定比

的天真方法更有效率
if (x == first value) ... 
else if (x == second value) ... 
else if (x == third value) ... 
..
else if (x == nth value) ... 
else ... 

如果将某些值放在if-else链的开头,这可能稍微好一点,但是假设您可以在运行代码之前确定最常用的值。

如果性能对您的情况很重要,那么您需要对这两种选择进行基准测试。但我的猜测是,只需将代码编写为交换机就可以使代码更清晰,同时运行至少同样快,如果不是更快。

答案 1 :(得分:2)

编译器当然可以将任何C / C ++开关转换为跳转表,但编译器可以提高效率。问问自己,如果我正在编写编译器,我会做什么?我只是为switch / case语句构建一个解析树?我研究过编译器设计和构造,这里有一些决定,

如何帮助编译器决定实现跳转表:

  • 案例值是小整数(0,1,2,3,...)
  • 案例值处于紧凑范围内(几个洞,记得默认是一个选项)
  • 有足够的案例可以使优化变得有价值(&gt; N,检查你的编译器源以找到常量)
  • 如果范围紧凑(例如:1000,1001,1002,1003,1004,1005等),聪明的编译器可以为跳转索引减去/添加常量
  • 避免落实和转移控制(转到,继续)
  • 每个案件结尾只有一次休息

虽然编译器之间的机制可能不同,但编译器本质上是创建未命名的函数(好吧,也许不是函数,因为编译器可能会使用跳转到代码块并跳出代码块,或者可能很聪明并使用jsr并返回)

获取跳转表的某种方法是编写它。它是一个指向函数的指针数组,由你想要的值索引。

如何?

为函数指针Understanding typedefs for function pointers in C

定义typedef

typedef void(* FunkPtr)(double a1,double a2);

FunkPtr JumpTable[] = {
    function_name_0,
    function_name_1,
    function_name_2,
    ...
    function_name_n
};

当然,您已经定义了function_name_ {0..n},因此编译器可以找到要唤起的函数的地址。

我会将函数指针和边界检查的调用作为读者的练习。