ISO C99 / C11では、ユニオンベースの型パンニングが有効であるため、非配列へのポインターにインデックスを付ける代わりにそれを使用できます(他のさまざまな回答を参照してください)。
ISO C ++では、ユニオンベースの型パンニングは許可されていません。 GNU C ++は拡張機能として機能しますが、一般的にGNU拡張機能をサポートしていない他の一部のコンパイラーは、共用体型パンニングをサポートしています。しかし、これは厳密に移植可能なコードを書くのに役立ちません。
gccとclangの現在のバージョンではswitch(idx)
、メンバーを選択するためにを使用してC ++メンバー関数を作成すると、コンパイル時の定数インデックスが最適化されますが、ランタイムインデックスにひどいブランチアセンブリが生成されます。これには本質的に問題はありません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]
対照的に、switch()
C ++のforS を使用した@Slavaの回答では、ランタイム変数インデックスに対して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]