我真的很惊讶我发现这个问题已经问到了。我想知道switch语句占用了多少代码空间,如果使用const查找表对我的需求更有效。
typedef struct container{
type1 a;
type2 b;
type3 c;
}container;
static container d;
//option A
void foo(int num)
{
void* x;
switch (num)
{
case 1:
x = &d->a;
break;
case 2:
x = &d->b;
break;
case 3:
x = &d->c;
break;
default:
x = NULL;
break;
}
// do something with x
}
// option B
const void* lookup_table[] = {
d.a,
d.b,
d.c,
NULL
};
void foo(int num)
{
void* x = lookup_table[num];
// do something with x
}
switch语句如何分解为汇编,代码空间会有多大?是否值得使用查找表而不是使用switch语句?
答案 0 :(得分:2)
如果您可以将交换机重写为查找表中的简单查找,那么这可能是最佳解决方案,特别是如果可能的索引是密集的,因为它也可能更具可读性。 (如果可能的索引不密集,你可以浪费空间或使用更复杂的查找技术:两级表,哈希表,二进制搜索到排序列表。这些可能比switch语句更好,但会更少但是,一个好的编译器会努力匹配效率,但是其中一些编译器会产生与你完全相同的代码。
但在通常情况下,您需要的不仅仅是查找值,而且switch语句几乎肯定更好。一个好的编译器会将switch语句编译成上面提到的策略之一,并且考虑到目标平台的细节,它可能比你对最优解决方案的了解更多。
特别是,由于调用函数的开销,将switch语句转换为函数指针的索引查找然后通过函数指针调用可能比switch语句慢得多。使用switch语句,编译器可能会生成一个分支表,其中查找代码与手工编写的代码非常相似,但在查找之后执行的操作是简单的分支而不是函数调用。
答案 1 :(得分:1)
这个问题没有确切的含义。优化编译器(通常)至少一次编译整个函数(通常是整个翻译单元)。
R.Sayle在编译开关时阅读this paper。您将了解到有几种竞争策略(跳转表,平衡树,条件移动,哈希跳转表等等),其中一些可以合并。
相信您的optimizing compiler ,以便为编译切换代码做出足够的选择。对于GCC,如果要查看生成的汇编程序,请使用bundle exec sidekiq -r ./script.rb
进行编译,或者添加gcc -Wall -O2 -march=native
(和/或将-fverbose-asm -S
替换为-O2
)。另请了解-O3
等...
当然,为了进行基准测试和生产代码,您应该始终要求编译器进行优化。
请注意,作为扩展名(Clang/LLVM也接受...)GCC有labels as values(间接gcc -flto -O3
s)。使用它们,您可以强制使用跳转表,或者使用threaded code。这并不总能让您的代码更快(例如,因为branch prediction)。
答案 2 :(得分:1)
查看帖子的另一种方式:
void foo(int num) { void* x; switch (num)...
可以很好地处理num
范围之外的1,2,3
。
void foo(int num) { void* x = lookup_table[num];
超出num
范围时, 0,1,2,3
有未定义的行为。
有些人可能会说num
范围不是问题。但是帖子中没有说明这一点。所以它是代码维护 - 许多未说明的,暗示的,有时是错误的假设条件。
是否值得使用查找表而不是使用switch语句?
对于值得维护,我会选择switch()
。
答案 3 :(得分:0)
正如其他人所说,现代优化编译器将尝试选择一种好的策略来将交换机编译成更有效的代码。 Hans Wennborg在2015年LLVM开发者大会上就recent switch lowering improvements发表了演讲,简要介绍了这一主题。
更好的是让编译器完成其工作并决定最可读的解决方案而不是您认为最有效的解决方案。
如果您想查看Clang为您的切换文件生成的代码,可以使用-S
或-S -emit-llvm
。