假设你使用的编译器支持C99(甚至只支持stdint. h),有没有理由不使用固定宽度的整数类型,比如uint8_t?
我知道的一个原因是,在处理字符时使用char
s比使用(u)int8_t
s更有意义,如this question中所提到的。
但是,如果你打算存储一个数字,什么时候你想使用一个你不知道它有多大的类型呢?也就是说,在什么情况下,你想把一个数字存储在unsigned short
中,而不知道它是8位、16位还是32位,而不是使用uint16t
?
接下来,是使用固定宽度的整数,还是使用普通的整数类型,并且在需要知道它们使用了多少字节的地方使用sizeof
?
6条答案
按热度按时间wbrvyc0a1#
实际上,存储一个数字而不需要知道类型的确切大小是很常见的。在我的程序中有很多数量,我可以合理地假设不会超过20亿,或者强制他们不超过20亿。但这并不意味着我需要一个精确的32位类型来存储它们,任何可以计数到至少20亿的类型都可以。
如果你想写可移植性很强的代码,你必须记住固定宽度类型都是可选的。
在C99实现中,
CHAR_BIT
大于8
,则不存在int8_t
。标准禁止它存在,因为它必须有填充位,而intN_t
类型被定义为没有填充位(7.18.1.1/1)。uint8_t
因此也被禁止,因为(谢谢,哇)不允许在没有int8_t
的情况下定义uint8_t
。因此,在非常可移植的代码中,如果你需要一个能够保存最多127个值的有符号类型,那么你应该根据你是否想让编译器来创建它,使用
signed char
,int
,int_least8_t
或int_fast8_t
之一:signed char
或int
)int
)int_least8_t
或signed char
)int_fast8_t
或int
)无符号类型也是如此,最多可达255,包括
unsigned char
、unsigned int
、uint_least8_t
和uint_fast8_t
。如果你需要在非常可移植的代码中进行模256运算,那么你可以自己取模,屏蔽位,或者用位域玩游戏。
在实践中,大多数人从来不需要编写可移植的代码。目前,
CHAR_BIT > 8
只在专用硬件上出现,而您的通用代码不会在其上使用。当然,这在未来可能会改变,但如果真的发生了,我怀疑有太多的代码对Posix和/或Windows(两者都保证CHAR_BIT == 8
)做出了假设,处理代码的不可移植性将是将代码移植到新平台的巨大努力的一小部分。任何这样的实现可能都要担心如何连接到互联网(处理八位字节),而不是担心如何启动和运行代码:-)如果你假设
CHAR_BIT == 8
无论如何,那么我不认为有任何特别的理由来避免(u)int8_t
,除非你想让代码在C89中工作。即使在C89中,为特定的实现找到或编写stdint.h
的版本也不是那么困难。但是,如果你可以轻松地编写代码,只要求类型可以容纳255
,而不是要求它不能容纳256
,那么你也可以避免对CHAR_BIT == 8
的依赖。xdnvmnnf2#
一个尚未提到的问题是,虽然使用固定大小的整数类型意味着如果编译器对
int
、long
等使用不同的大小,则变量的大小不会改变,但它不一定能保证代码在具有不同整数大小的机器上的行为相同,* 即使定义了大小 *。例如,给定声明
uint32_t i;
,当i
为零时,表达式(i-1) > 5
的行为将根据uint32_t
是否小于int
而变化。在系统上,例如int
是64位(uint32_t
类似于long short
),变量i
将提升为int
;减法和比较将被执行为带符号(-1小于5)。在int
为32位的系统上,减法和比较将按照unsigned int
执行(减法将产生一个非常大的数字,大于5)。我不知道有多少代码依赖于这样一个事实,即即使在没有类型转换的情况下,涉及无符号类型的表达式的中间结果也需要 Package (恕我直言,如果需要 Package 行为,程序员应该包括一个类型转换)
(uint32_t)(i-1) > 5
),但目前的标准不允许任何回旋余地。我想知道,如果一个规则至少允许编译器在没有类型转换或类型强制的情况下将操作数提升为更长的整数类型,那么会产生什么问题[例如:给定uint32_t i,j
,需要像j = (i+=1) >> 1;
这样的赋值来截断溢出,就像j = (uint32_t)(i+1) >> 1;
一样,但j = (i+1)>>1
不会]?或者,就这一点而言,编译器制造商要保证任何整数类型的表达式,其中间结果都可以适合最大的有符号类型,并且不涉及非常数量的右移,将产生相同的结果,就像所有计算都在该类型上执行一样,有多难?在一台int
是32位的机器上,这对我来说似乎相当令人讨厌:字符串
清除
a
和c
各一位,但清除b
的前33位;大多数编译器将不给予关于第二表达式的任何“不同”的提示。6jygbczu3#
标准整数类型的宽度确实可以从一个平台改变到另一个平台,但它的最小 * 宽度 * 不会改变。
例如,C标准规定
int
至少为16-bit
,long
至少为32-bit
宽。如果你在存储对象时没有一些大小限制,你可以让它的实现。例如,如果最大有符号值适合
16-bit
,则可以使用int
。然后让实现最终决定实现所针对的体系结构的自然int
宽度。1zmg4dgp4#
代码应该向普通读者(以及程序员自己)揭示什么是重要的。它只是一些整数或 * 无符号整数 * 或甚至 * 有符号整数 *。大小也是如此。某些变量默认为16位对算法真的很重要吗?或者这只是不必要的微观管理和失败的优化尝试?
这就是编程成为一门艺术的原因--展示什么是重要的。
w1jd8yoj5#
当您对宽度进行假设时,您应该只使用固定宽度类型。
uint8_t
和unsigned char
在大多数平台上是相同的,但并非在所有平台上都相同。使用uint8_t
强调了这样一个事实,即您假设一个具有8位char
的架构,并且不会在其他架构上编译,因此这是一个特性。否则,我会使用“语义”
typedef
,如size_t
,uintptr_t
,ptrdiff_t
,因为它们更好地反映了您对数据的想法。我几乎从不直接使用基本类型,int
只用于错误返回,我不记得曾经使用过short
。uint8_t
,如果存在的话,必须是unsigned char
,并且不能只是char
,即使该类型是无符号的。这来自于7.20.1 p1中的要求,即所有intN_t
和uintN_t
必须是 * 相应的 * 有符号和无符号类型。字符类型的唯一这样的对是signed char
和unsigned char
。niknxzdl6#
有很多原因可以解释为什么要使用,让我们称它们为 * 语义 * 类型,如
int
或char
,而不是固定宽度的类型,如uint8_t
:匹配已有API
标准C库在任何地方都使用
char*
。为什么要混淆用户(并引入可能的bug?)在与该API对话时使用不同的类型?类似地,
printf()
字符串是根据这些语义类型定义的。如果你想打印一个固定大小的类型,你需要像PRIu64
等宏。在stdint.h
中,让您获得正确的格式字符串,以便使用旧的printf
字符串打印固定大小的类型。速度
通常选择语义类型,以便它们最适合当前CPU的性能特征。它们可能会被撞到比你选择的尺寸稍大的尺寸,因为这是你的CPU上的寄存器尺寸,可以保存你不必要的转换等。
现在这是一个有争议的答案…这是最初的意图,但由于
stdint
在早期的C/C++中不可用,许多平台(如32位Windows或macOS X)只能保证int
和long
的大小。因此,在64位迁移期间,其中一些大小保持不变(导致有趣的新类型,如long long
等)。这就是为什么我们有least
和fast
类型。代码移植
语义类型在64位平台上可以比在32位平台上更大(例如,以允许数组索引填充所有存储器)。因此,如果你在不同的平台上运行,使用语义类型(根据我的定义,在可用的地方包括
size_t
)而不是固定类型意味着你可以利用更好的硬件,而不是增加任意的限制。当然,这只会使你的算法具有可移植性。如果需要将数据序列化为字节并在不同平台之间交换,这可能会使代码具有可移植性,但不会使网络数据包或输出文件具有可移植性。因此,对于这种情况,您实际上希望坚持使用固定类型,以便数据保持可移植性,代价是代码运行速度非常慢,或者无法在某些平台上编译。
**评论:**不要问我为什么不引入
int64_t
、int32_t
等格式字符串。也许他们没有信了也许太多的代码库定义了他们自己的格式字符串,并会破坏?