在ISO C99 / C11中,基于联合的类型绑定是合法的,因此您可以使用它代替索引非数组的指针(请参阅其他答案)。
ISO C ++不允许基于联合的类型处理。 作为扩展,GNU C ++确实可以,并且我认为一般不支持GNU扩展的其他一些编译器也支持联合类型对齐。但这并不能帮助您编写严格的可移植代码。
在当前版本的gcc和clang中,使用a switch(idx)
来选择成员来编写C ++成员函数将优化编译时常数索引,但会为运行时索引产生可怕的分支asm。这没有什么天生的错误switch()
。这只是当前编译器中未进行优化的错误。他们可以有效地编译Slava的switch()函数。
解决方案/解决方法是用另一种方式做:给您的类/结构一个数组成员,并编写访问器函数以将名称附加到特定元素。
struct array_data
{
int arr[3];
int &operator[]( unsigned idx ) {
// assert(idx <= 2);
//idx = (idx > 2) ? 2 : idx;
return arr[idx];
}
int &a(){ return arr[0]; } // TODO: const versions
int &b(){ return arr[1]; }
int &c(){ return arr[2]; }
};
我们可以在Godbolt编译器资源管理器上查看不同用例的asm输出。这些是完整的x86-64 System V功能,省略了尾随的RET指令,以更好地显示当它们内联时将得到的结果。ARM / MIPS /类似的东西。
# asm from g++6.2 -O3
int getb(array_data &d) { return d.b(); }
mov eax, DWORD PTR [rdi+4]
void setc(array_data &d, int val) { d.c() = val; }
mov DWORD PTR [rdi+8], esi
int getidx(array_data &d, int idx) { return d[idx]; }
mov esi, esi # zero-extend to 64-bit
mov eax, DWORD PTR [rdi+rsi*4]
相比之下,@ Slava使用switch()
C ++ 的答案使asm成为运行时变量索引。(上一个Godbolt链接中的代码)。
int cpp(data *d, int idx) {
return (*d)[idx];
}
# gcc6.2 -O3, using `default: __builtin_unreachable()` to promise the compiler that idx=0..2,
# avoiding an extra cmov for idx=min(idx,2), or an extra branch to a throw, or whatever
cmp esi, 1
je .L6
cmp esi, 2
je .L7
mov eax, DWORD PTR [rdi]
ret
.L6:
mov eax, DWORD PTR [rdi+4]
ret
.L7:
mov eax, DWORD PTR [rdi+8]
ret
与基于C(或GNU C ++)联合的类型修剪版本相比,这显然是可怕的:
c(type_t*, int):
movsx rsi, esi # sign-extend this time, since I didn't change idx to unsigned here
mov eax, DWORD PTR [rdi+rsi*4]