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 ++) 공용체 기반 유형 punning 버전에 비해 분명히 끔찍합니다.
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]