“default”语句会影响跳转表优化吗?

11
在我的代码中,我习惯编写包含如下assert的回退默认情况,以防止在语义发生变化时忘记更新switch。
switch(mode) {
case ModeA: ... ;
case ModeB: ... ;
case .. /* many of them ... */
default: {
  assert(0 && "Unknown mode!");
  return ADummyValue();
}
};

现在我想知道人工后备检查的默认情况是否会干扰跳转表的生成?假设"ModeA"和"ModeB"等是连续的,因此编译器可以将它们优化为一个表。由于"default"情况包含一个实际的"return"语句(因为断言会在发布模式下消失,编译器会抱怨缺少返回语句),因此似乎不太可能使编译器优化默认分支。

最好的方法是什么?有人建议我用空指针解引用替换"ADummyValue",这样,在存在未定义行为的情况下,编译器可以省略警告缺少返回语句。有更好的解决方法吗?


考虑到 assert,最好是使用 throwterminate 而不是 return - Mooing Duck
7个回答

3

不错!如果GCC也有这个就好了 :) - Johannes Schaub - litb
3
@ Johannes 谷歌是你的好朋友 :) http://en.chys.info/2010/07/counterpart-of-assume-in-gcc/,这篇文章介绍了GCC中 "assume" 的对应词。 - ruslik

3

至少在我所看过的编译器中,答案通常是否定的。大多数编译器将像这样的switch语句编译成类似于以下代码的代码:

if (mode < modeA || mode > modeLast) {
    assert(0 && "Unknown mode!");
    return ADummyValue();
}
switch(mode) { 
    case modeA: ...;
    case modeB: ...;
    case modeC: ...;
    // ...
    case modeLast: ...;
}

真遗憾,我必须为了速度而牺牲验证 :( - Johannes Schaub - litb
@Johannes:函数指针数组能否像跳转表一样工作?从数组中查找函数指针并调用它。(跳转表包含一个寄存器跳转和一个固定跳转;函数指针数组应该是一个寄存器加载和一个寄存器跳转,我猜是这样吧?) - rwong

2
如果您使用的是“默认”(哈!)<assert.h>,那么定义已经与NDEBUG宏绑定在一起了,因此可以考虑简单地:
    case nevermind:
#if !defined(NDEBUG)
    default:
        assert("can" && !"happen");
#endif
    }

这是我推荐的做法。它不会牺牲速度,你仍然可以测试未考虑到的类型,并且你仍然可以获得编译器警告。 - Trevor Hickey

1

我只看到一种解决方案,以防优化实际上被干扰:在默认情况下使用臭名昭著的“#ifndef NDEBUG”。虽然不是最好的技巧,但在这种情况下很清楚。

顺便问一下:你已经查看过编译器在有和没有默认情况下的处理结果了吗?


@Johannes:至少Jerry Coffin做到了。 - stefaanv

1
如果您有一个永远不应该到达的状态,那么您应该终止程序,因为它刚刚到达了一个意外的状态,即使在发布模式下(您可能只是更加外交,并在崩溃之前保存用户数据并执行所有其他好的操作)。
请不要过于关注微小的优化,除非您实际上已经使用分析器测量出需要它们。

但我不想在发布模式下检查无法到达的状态。我的假设是代码不包含错误。当然,这很可能是一个错误的假设,但对我来说似乎有必要摆脱检查无法到达的状态(而且这非常关键:脚本语言运行时的名称查找例程、隐式转换例程等)。 - Johannes Schaub - litb

1
处理这个问题的最佳方式不是禁用断言。这样你也可以留意可能存在的错误。有时候,让应用程序崩溃并显示清晰的错误信息比继续运行更好。

0
使用编译器扩展:
// assume.hpp
#pragma once

#if defined _MSC_VER
#define MY_ASSUME(e) (__assume(e), (e) ? void() : void())
#elif defined __GNUC__
#define MY_ASSUME(e) ((e) ? void() : __builtin_unreachable())
#else   // defined __GNUC__
#error unknown compiler
#endif  // defined __GNUC__

-

// assert.hpp
#include <cassert>
#include "assume.hpp"

#undef MY_ASSERT
#ifdef NDEBUG
#define MY_ASSERT MY_ASSUME
#else   // NDEBUG
#define MY_ASSERT assert
#endif  // NDEBUG

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接