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