Trong ISO C99 / C11, đánh lừa kiểu dựa trên liên hợp là hợp pháp, vì vậy bạn có thể sử dụng điều đó thay vì lập chỉ mục con trỏ đến không phải mảng (xem nhiều câu trả lời khác).
ISO C ++ không cho phép xử lý kiểu dựa trên liên hợp. GNU C ++, như một phần mở rộng , và tôi nghĩ rằng một số trình biên dịch khác không hỗ trợ phần mở rộng GNU nói chung có hỗ trợ kiểu kết hợp-punning. Nhưng điều đó không giúp bạn viết mã di động nghiêm ngặt.
Với các phiên bản gcc và clang hiện tại, việc viết một hàm thành viên C ++ bằng cách sử dụng a switch(idx)
để chọn một thành viên sẽ tối ưu hóa cho các chỉ số không đổi thời gian biên dịch, nhưng sẽ tạo ra nhiều nhánh khủng khiếp cho các chỉ số thời gian chạy. Không có gì sai với switch()
điều này; đây chỉ đơn giản là một lỗi tối ưu hóa bị bỏ sót trong các trình biên dịch hiện tại. Họ có thể biên dịch hàm switch () của Slava một cách hiệu quả.
Giải pháp / cách giải quyết này là thực hiện theo cách khác: cung cấp cho lớp / struct của bạn một thành viên mảng và viết các hàm truy cập để đính kèm tên cho các phần tử cụ thể.
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]; }
};
Chúng ta có thể xem đầu ra asm cho các trường hợp sử dụng khác nhau, trên trình khám phá trình biên dịch Godbolt . Đây là các hàm System V hoàn chỉnh của x86-64, với lệnh RET ở cuối bị bỏ qua để hiển thị tốt hơn những gì bạn nhận được khi chúng nội dòng. ARM / MIPS / bất cứ thứ gì sẽ tương tự.
# 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]
Để so sánh, câu trả lời của @ Slava sử dụng a switch()
cho C ++ làm cho asm giống như thế này cho một chỉ mục biến thời gian chạy. (Mã trong liên kết Godbolt trước).
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
Điều này rõ ràng là khủng khiếp, so với phiên bản xảo quyệt kiểu liên minh dựa trên C (hoặ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]