【问题标题】:c switch and jump tablesc 切换和跳转表
【发布时间】:2013-06-08 08:34:00
【问题描述】:

据我了解,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 语句,所以至少我获得了一些可读性。

【问题讨论】:

  • 1) 这很可能取决于您的编译器。你用的是哪个? 2)你为什么关心?您应该相信您的编译器会尽其所能使用您编写的代码。跳表可能不是所有情况下的最佳选择。不能完全回答您的问题,但您可能对ols.fedoraproject.org/GCC/Reprints-2008/sayle-reprint.pdf 感兴趣
  • @BoBTFish:感谢指点,感谢。
  • @BoBTFish 谢谢,如果我有时间的话,这似乎是一本不错的读物(:
  • 你为什么在乎?编译器编写者可能比您更了解目标硬件,并且编译器通常会生成尽可能好的代码,无论是跳转表、链式测试还是其他任何东西。
  • 为什么?跳转表很容易编写。如果你想写一个。 switch/case 很清楚,对于少数情况,它只是几个 cmp/jmp 指令。一般来说,相信你的编译器。

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


【解决方案1】:

一个好的编译器可以并且会在跳转表、链式 if/else 或组合之间进行选择。设计不佳的编译器可能不会做出这样的选择——甚至可能会为开关块生成非常糟糕的代码。但是任何体面的编译器都应该为开关块生成有效的代码。 T

这里的主要决定因素是编译器可能会在数字相距很远时选择 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)
     {
         ...
     }
 }

如果我们有很多案例,这种方法会嵌套很深,人类可能会在三到四个深度级别后迷路(请记住,每个 if 都从范围中间的某个点开始),但是它通过 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 链的开头,这可能会稍微好一些,但前提是您可以在运行代码之前确定最常见的值。

如果性能对您的情况至关重要,那么您需要对这两个备选方案进行基准测试。但我的猜测是,仅将代码编写为开关将使代码更清晰,同时运行速度至少一样快,如果不是更快的话。

【讨论】:

    【解决方案2】:

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

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

    • case 值是小整数 (0,1,2,3,...)
    • case 值在一个紧凑的范围内(几个孔,记住默认是一个选项)
    • 有足够的情况使优化值得(> N,检查您的编译器源代码以找到常量)
    • 如果范围很紧凑(例如:1000、1001、1002、1003、1004、1005 等),聪明的编译器可能会在可跳转索引中减去/添加常量
    • 避免失败和控制权转移(转到、继续)
    • 每个案例结束时只有一次休息

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

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

    怎么做?

    为你的函数指针定义一个 typedef,Understanding typedefs for function pointers in C

    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},所以编译器可以找到要调用的函数的地址。

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

    【讨论】:

      猜你喜欢
      • 2015-12-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-04-13
      • 2019-05-02
      • 1970-01-01
      • 2015-11-08
      • 2020-05-07
      相关资源
      最近更新 更多