Dalam ISO C99 / C11, jenis-punning berbasis union adalah legal, jadi Anda dapat menggunakannya daripada mengindeks pointer ke non-array (lihat berbagai jawaban lain).
ISO C ++ tidak mengizinkan jenis punning berbasis gabungan. GNU C ++ memang, sebagai ekstensi , dan saya pikir beberapa kompiler lain yang tidak mendukung ekstensi GNU secara umum mendukung union type-punning. Tetapi itu tidak membantu Anda menulis kode yang sangat portabel.
Dengan versi gcc dan clang saat ini, menulis fungsi anggota C ++ menggunakan a switch(idx)
untuk memilih anggota akan mengoptimalkan indeks konstan waktu kompilasi, tetapi akan menghasilkan asm bercabang yang mengerikan untuk indeks waktu proses. Tidak ada yang salah dengan switch()
hal ini; ini hanyalah bug pengoptimalan yang terlewat di kompiler saat ini. Mereka bisa mengkompilasi fungsi switch () Slava secara efisien.
Solusi / solusi untuk ini adalah melakukannya dengan cara lain: berikan kelas / struct Anda anggota array, dan tulis fungsi pengakses untuk melampirkan nama ke elemen tertentu.
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]; }
};
Kita dapat melihat keluaran asm untuk kasus penggunaan yang berbeda, pada penjelajah kompilator Godbolt . Ini adalah fungsi Sistem V x86-64 lengkap, dengan instruksi RET tambahan dihilangkan untuk lebih menunjukkan apa yang Anda dapatkan ketika mereka sebaris. ARM / MIPS / apa pun yang serupa.
# 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]
Sebagai perbandingan, jawaban @ Slava menggunakan a switch()
for C ++ membuat asm seperti ini untuk indeks variabel runtime. (Kode di tautan Godbolt sebelumnya).
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
Ini jelas mengerikan, dibandingkan dengan versi pelesetan tipe berbasis serikat C (atau 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]