Theo hiểu biết của tôi, int
ban đầu được cho là một kiểu số nguyên "gốc" với đảm bảo bổ sung rằng nó phải có kích thước ít nhất là 16 bit - một thứ được coi là kích thước "hợp lý" hồi đó.
Khi nền tảng 32 bit trở nên phổ biến hơn, chúng ta có thể nói rằng kích thước "hợp lý" đã thay đổi thành 32 bit:
- Windows hiện đại sử dụng 32-bit
int
trên tất cả các nền tảng.
- POSIX đảm bảo rằng
int
ít nhất là 32 bit.
- C #, Java có kiểu
int
được đảm bảo chính xác là 32 bit.
Nhưng khi nền tảng 64-bit trở thành tiêu chuẩn, không ai mở rộng int
thành số nguyên 64-bit vì:
- Tính di động: rất nhiều mã phụ thuộc vào
int
kích thước 32 bit.
- Tiêu thụ bộ nhớ: tăng gấp đôi mức sử dụng bộ nhớ cho mọi
int
trường hợp có thể là không hợp lý đối với hầu hết các trường hợp, vì trong hầu hết các trường hợp, con số được sử dụng nhỏ hơn nhiều dưới 2 tỷ.
Bây giờ, tại sao bạn muốn uint32_t
đến uint_fast32_t
? Vì lý do tương tự các ngôn ngữ C # và Java luôn sử dụng số nguyên có kích thước cố định: lập trình viên không viết mã khi nghĩ về các kích thước có thể có của các loại khác nhau, họ viết cho một nền tảng và mã kiểm tra trên nền tảng đó. Hầu hết mã phụ thuộc hoàn toàn vào kích thước cụ thể của kiểu dữ liệu. Và đây là lý do tại sao uint32_t
là một lựa chọn tốt hơn cho hầu hết các trường hợp - nó không cho phép bất kỳ sự mơ hồ nào liên quan đến hành vi của nó.
Hơn nữa, có uint_fast32_t
thực sự là loại nhanh nhất trên nền tảng có kích thước bằng hoặc lớn hơn đến 32 bit? Không hẳn vậy. Hãy xem xét trình biên dịch mã này của GCC cho x86_64 trên Windows:
extern uint64_t get(void);
uint64_t sum(uint64_t value)
{
return value + get();
}
Lắp ráp đã tạo trông như thế này:
push
sub $0x20,
mov
callq d <sum+0xd>
add
add $0x20,
pop
retq
Bây giờ nếu bạn thay đổi get()
giá trị trả về của thành uint_fast32_t
(là 4 byte trên Windows x86_64), bạn sẽ nhận được điều này:
push %rbx
sub $0x20,%rsp
mov %rcx,%rbx
callq d <sum+0xd>
mov %eax,%eax ; <-- additional instruction
add %rbx,%rax
add $0x20,%rsp
pop %rbx
retq
Lưu ý rằng mã được tạo gần như giống nhau ngoại trừ mov %eax,%eax
lệnh bổ sung sau lệnh gọi hàm có nghĩa là để mở rộng giá trị 32 bit thành giá trị 64 bit.
Không có vấn đề như vậy nếu bạn chỉ sử dụng các giá trị 32-bit, nhưng có thể bạn sẽ sử dụng các giá trị có size_t
biến (kích thước mảng có thể là?) Và đó là 64 bit trên x86_64. Trên Linux uint_fast32_t
là 8 byte, vì vậy tình hình sẽ khác.
Nhiều lập trình viên sử dụng int
khi họ cần trả về giá trị nhỏ (giả sử trong phạm vi [-32,32]). Điều này sẽ hoạt động hoàn hảo nếu int
là kích thước số nguyên gốc của nền tảng, nhưng vì nó không phải trên nền tảng 64-bit, nên một loại khác phù hợp với loại gốc của nền tảng là lựa chọn tốt hơn (trừ khi nó thường được sử dụng với các số nguyên khác có kích thước nhỏ hơn).
Về cơ bản, bất kể tiêu chuẩn nói gì, uint_fast32_t
vẫn bị hỏng trên một số triển khai. Nếu bạn quan tâm đến hướng dẫn bổ sung được tạo ở một số nơi, bạn nên xác định kiểu số nguyên "gốc" của riêng mình. Hoặc bạn có thể sử dụng size_t
cho mục đích này, vì nó thường sẽ phù hợp với native
kích thước (Tôi không bao gồm các nền tảng cũ và ít người biết đến như 8086, chỉ các nền tảng có thể chạy Windows, Linux, v.v.).
Một dấu hiệu khác cho thấy int
được cho là kiểu số nguyên gốc là "quy tắc thăng hạng số nguyên". Hầu hết các CPU chỉ có thể thực hiện các hoạt động trên nguyên bản, do đó, CPU 32 bit thường chỉ có thể thực hiện các phép cộng, trừ, v.v. 32 bit (CPU Intel là một ngoại lệ ở đây). Các loại số nguyên có kích thước khác chỉ được hỗ trợ thông qua hướng dẫn tải và lưu trữ. Ví dụ: giá trị 8 bit nên được tải bằng lệnh "tải 8 bit có dấu" hoặc "tải 8 bit không dấu" thích hợp và sẽ mở rộng giá trị thành 32 bit sau khi tải. Nếu không có quy tắc thăng hạng số nguyên, trình biên dịch C sẽ phải thêm một chút mã cho các biểu thức sử dụng kiểu nhỏ hơn kiểu gốc. Thật không may, điều này không còn tồn tại với kiến trúc 64-bit vì các trình biên dịch giờ đây phải phát ra các lệnh bổ sung trong một số trường hợp (như được hiển thị ở trên).