据我了解,
int
最初应该是一种“本地”的整数类型,并且保证至少为16位大小-当时被认为是“合理”的大小。随着32位平台变得更加普遍,“合理”的大小已经变成了32位:
- 现代 Windows 在所有平台上都使用32位的
int
。
- POSIX 保证
int
至少为32位。
- C#、Java 有类型
int
,确保其大小恰好为32位。
但是当64位平台成为常态时,没有人将
int
扩展为64位整数,原因是:
- 可移植性:许多代码依赖于
int
的32位大小。
- 内存消耗:每个
int
增加一倍内存使用量可能对大多数情况来说不太合理,因为在大多数情况下,使用的数字要小得多,不到20亿。
那么,为什么你会更喜欢
uint32_t
而不是
uint_fast32_t
?与 C# 和 Java 总是使用固定大小整数的原因相同:程序员编写代码时不考虑不同类型可能的大小,他们为一个平台编写并在该平台上测试代码。大多数代码隐式地依赖于特定数据类型的大小。这就是为什么
uint32_t
是大多数情况下更好的选择-它不允许任何关于其行为的歧义。
此外,在大小等于或大于32位的平台上,
uint_fast32_t
真的是最快的类型吗?并不是。考虑一下 GCC 在 Windows 上为 x86_64 编译的以下代码:
extern uint64_t get(void);
uint64_t sum(uint64_t value)
{
return value + get();
}
生成的汇编代码如下:
push %rbx
sub $0x20,%rsp
mov %rcx,%rbx
callq d <sum+0xd>
add %rbx,%rax
add $0x20,%rsp
pop %rbx
retq
如果你将get()
的返回值更改为uint_fast32_t
(在Windows x86_64上为4字节),则会得到以下结果:
push %rbx
sub $0x20,%rsp
mov %rcx,%rbx
callq d <sum+0xd>
mov %eax,%eax
add %rbx,%rax
add $0x20,%rsp
pop %rbx
retq
请注意生成的代码几乎相同,除了在函数调用后添加了一个
mov %eax, %eax
指令,该指令旨在将32位值扩展为64位值。
如果您只使用32位值,则不会出现此类问题,但是您可能会使用
size_t
变量(可能是数组大小),而这些变量在x86_64上为64位。在Linux上,
uint_fast32_t
为8个字节,因此情况有所不同。
许多程序员在需要返回小值(比如范围在[-32, 32]内)时使用
int
。如果
int
是平台本机整数大小,则完美地运行,但由于它不是64位平台上的本机类型,因此另一种与平台本机类型匹配的类型是更好的选择(除非它经常与较小的其他整数一起使用)。
基本上,无论标准说什么,
uint_fast32_t
在某些实现上都是错误的。如果您关心在某些地方生成的附加指令,则应定义自己的“本机”整数类型。或者,您可以将
size_t
用于此目的,因为它通常与
native
大小匹配(我不包括旧的和晦涩的平台,例如8086,只有可以运行Windows、Linux等平台)。
另一个显示
int
应该是本机整数类型的迹象是“整数提升规则”。大多数CPU只能在本机上执行操作,因此32位CPU通常只能执行32位加法,减法等操作(Intel CPU在这里是个例外)。其他大小的整数类型仅通过加载和存储指令支持。例如,8位值应使用适当的“加载8位有符号”或“加载8位无符号”指令进行加载,并且在加载后将值扩展为32位。如果没有整数提升规则,C编译器将不得不为使用小于本机类型的类型的表达式添加更多代码。不幸的是,在64位架构中,这种情况不再成立,因为编译器现在必须在某些情况下发出附加指令(如上所示)。
uint32_fast_t
,如果我理解正确的话,它至少是32位(这意味着可能会更多?这听起来有点误导)。目前我在我的项目中使用uint32_t
和其它类型,因为我要将这些数据打包并发送到网络上,我希望发送方和接收方都知道字段的确切大小。但这似乎不是最健壮的解决方案,因为某些平台可能没有实现uint32_t
,但显然我所有的平台都支持,所以我对自己的做法感到满意。 - yanouint32_t
类型并没有涉及到这个问题(而且很遗憾目前没有像uint32_t_be
和uint32_t_le
这样更适用于几乎所有情况的类型来代替uint32_t
)。 - Brendan