In ISO C99 / C11, il gioco di parole basato sull'unione è legale, quindi puoi usarlo invece di indicizzare i puntatori a non array (vedi varie altre risposte).
ISO C ++ non consente il gioco di parole basato sull'unione. GNU C ++ lo fa, come estensione , e penso che alcuni altri compilatori che non supportano le estensioni GNU in generale supportano la tipizzazione di tipo union. Ma questo non ti aiuta a scrivere codice strettamente portabile.
Con le versioni correnti di gcc e clang, scrivere una funzione membro C ++ usando a switch(idx)
per selezionare un membro ottimizzerà gli indici delle costanti in fase di compilazione, ma produrrà terribili asm ramificati per gli indici di runtime. Non c'è niente di intrinsecamente sbagliato switch()
in questo; questo è semplicemente un bug di mancata ottimizzazione nei compilatori attuali. Potrebbero compilare la funzione switch () di Slava in modo efficiente.
La soluzione / rimedio a questo è farlo nell'altro modo: dare alla tua classe / struttura un membro dell'array e scrivere funzioni accessor per allegare nomi a elementi specifici.
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]; }
};
Possiamo dare uno sguardo all'output asm per diversi casi d'uso, sul compilatore Godbolt explorer . Queste sono funzioni complete x86-64 System V, con l'istruzione RET finale omessa per mostrare meglio cosa otterresti quando sono in linea. ARM / MIPS / qualunque cosa sarebbe simile.
# 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]
In confronto, la risposta di @ Slava usando a switch()
per C ++ rende asm come questo per un indice di variabile di runtime. (Codice nel precedente link 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
Questo è ovviamente terribile, rispetto alla versione di punning di tipo basata sull'unione C (o 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]