C语言 是否有任何理由不使用固定宽度的整数类型(例如uint8_t)?

jhdbpxl9  于 2023-08-03  发布在  其他
关注(0)|答案(6)|浏览(121)

假设你使用的编译器支持C99(甚至只支持stdint. h),有没有理由不使用固定宽度的整数类型,比如uint8_t?
我知道的一个原因是,在处理字符时使用char s比使用(u)int8_t s更有意义,如this question中所提到的。
但是,如果你打算存储一个数字,什么时候你想使用一个你不知道它有多大的类型呢?也就是说,在什么情况下,你想把一个数字存储在unsigned short中,而不知道它是8位、16位还是32位,而不是使用uint16t
接下来,是使用固定宽度的整数,还是使用普通的整数类型,并且在需要知道它们使用了多少字节的地方使用sizeof

wbrvyc0a

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 charintint_least8_tint_fast8_t之一:

  • 在C89中工作(signed charint
  • 避免算术表达式中意外的整数提升(int
  • 小(int_least8_tsigned char
  • 快速(int_fast8_tint

无符号类型也是如此,最多可达255,包括unsigned charunsigned intuint_least8_tuint_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的依赖。

xdnvmnnf

xdnvmnnf2#

一个尚未提到的问题是,虽然使用固定大小的整数类型意味着如果编译器对intlong等使用不同的大小,则变量的大小不会改变,但它不一定能保证代码在具有不同整数大小的机器上的行为相同,* 即使定义了大小 *。
例如,给定声明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位的机器上,这对我来说似乎相当令人讨厌:

uint64_t a,b,c;
  ...
  a &= ~0x40000000;
  b &= ~0x80000000;
  c &= ~0x100000000;

字符串
清除ac各一位,但清除b的前33位;大多数编译器将不给予关于第二表达式的任何“不同”的提示。

6jygbczu

6jygbczu3#

标准整数类型的宽度确实可以从一个平台改变到另一个平台,但它的最小 * 宽度 * 不会改变。
例如,C标准规定int至少为16-bitlong至少为32-bit宽。
如果你在存储对象时没有一些大小限制,你可以让它的实现。例如,如果最大有符号值适合16-bit,则可以使用int。然后让实现最终决定实现所针对的体系结构的自然int宽度。

1zmg4dgp

1zmg4dgp4#

代码应该向普通读者(以及程序员自己)揭示什么是重要的。它只是一些整数或 * 无符号整数 * 或甚至 * 有符号整数 *。大小也是如此。某些变量默认为16位对算法真的很重要吗?或者这只是不必要的微观管理和失败的优化尝试?
这就是编程成为一门艺术的原因--展示什么是重要的。

w1jd8yoj

w1jd8yoj5#

当您对宽度进行假设时,您应该只使用固定宽度类型。
uint8_tunsigned char在大多数平台上是相同的,但并非在所有平台上都相同。使用uint8_t强调了这样一个事实,即您假设一个具有8位char的架构,并且不会在其他架构上编译,因此这是一个特性。
否则,我会使用“语义”typedef,如size_tuintptr_tptrdiff_t,因为它们更好地反映了您对数据的想法。我几乎从不直接使用基本类型,int只用于错误返回,我不记得曾经使用过short

  • 编辑:* 在仔细阅读C11之后,我得出结论,uint8_t,如果存在的话,必须是unsigned char,并且不能只是char,即使该类型是无符号的。这来自于7.20.1 p1中的要求,即所有intN_tuintN_t必须是 * 相应的 * 有符号和无符号类型。字符类型的唯一这样的对是signed charunsigned char
niknxzdl

niknxzdl6#

有很多原因可以解释为什么要使用,让我们称它们为 * 语义 * 类型,如intchar,而不是固定宽度的类型,如uint8_t

匹配已有API

标准C库在任何地方都使用char*。为什么要混淆用户(并引入可能的bug?)在与该API对话时使用不同的类型?
类似地,printf()字符串是根据这些语义类型定义的。如果你想打印一个固定大小的类型,你需要像PRIu64等宏。在stdint.h中,让您获得正确的格式字符串,以便使用旧的printf字符串打印固定大小的类型。

速度

通常选择语义类型,以便它们最适合当前CPU的性能特征。它们可能会被撞到比你选择的尺寸稍大的尺寸,因为这是你的CPU上的寄存器尺寸,可以保存你不必要的转换等。
现在这是一个有争议的答案…这是最初的意图,但由于stdint在早期的C/C++中不可用,许多平台(如32位Windows或macOS X)只能保证intlong的大小。因此,在64位迁移期间,其中一些大小保持不变(导致有趣的新类型,如long long等)。这就是为什么我们有leastfast类型。

代码移植

语义类型在64位平台上可以比在32位平台上更大(例如,以允许数组索引填充所有存储器)。因此,如果你在不同的平台上运行,使用语义类型(根据我的定义,在可用的地方包括size_t)而不是固定类型意味着你可以利用更好的硬件,而不是增加任意的限制。
当然,这只会使你的算法具有可移植性。如果需要将数据序列化为字节并在不同平台之间交换,这可能会使代码具有可移植性,但不会使网络数据包或输出文件具有可移植性。因此,对于这种情况,您实际上希望坚持使用固定类型,以便数据保持可移植性,代价是代码运行速度非常慢,或者无法在某些平台上编译。

**评论:**不要问我为什么不引入int64_tint32_t等格式字符串。也许他们没有信了也许太多的代码库定义了他们自己的格式字符串,并会破坏?

相关问题