W ISO C99 / C11, punktowanie typu oparte na związkach jest legalne, więc można go używać zamiast indeksowania wskaźników do innych niż tablice (zobacz różne inne odpowiedzi).
ISO C ++ nie zezwala na punktowanie typów oparte na unii. GNU C ++ robi to jako rozszerzenie i myślę, że niektóre inne kompilatory, które generalnie nie obsługują rozszerzeń GNU, obsługują punning typu union. Ale to nie pomaga w pisaniu czysto przenośnego kodu.
W obecnych wersjach gcc i clang, napisanie funkcji składowej C ++ przy użyciu a switch(idx)
do wybrania elementu spowoduje optymalizację pod kątem stałych w czasie kompilacji, ale da straszny rozgałęziony asm dla indeksów czasu wykonywania. Nie ma w tym nic złego switch()
; jest to po prostu błąd brakującej optymalizacji w obecnych kompilatorach. Mogliby efektywnie działać kompilator switch () Slavy.
Rozwiązaniem / obejściem tego problemu jest zrobienie tego w inny sposób: nadanie klasie / strukturze składowej tablicy i napisanie funkcji akcesorów, aby dołączyć nazwy do określonych elementów.
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]; }
};
Możemy przyjrzeć się wynikom asm dla różnych przypadków użycia w eksploratorze kompilatora Godbolt . Są to kompletne funkcje Systemu V x86-64, z pominięciem końcowej instrukcji RET, aby lepiej pokazać, co otrzymasz, gdy są wbudowane. ARM / MIPS / cokolwiek byłoby podobne.
# 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]
Dla porównania, odpowiedź @ Slava za pomocą a switch()
for C ++ sprawia, że asm jest taki dla indeksu zmiennej czasu wykonywania. (Kod w poprzednim linku 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
Jest to oczywiście okropne w porównaniu do wersji punningowej opartej na związkach typu C (lub 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]