62

假设您使用的是支持 C99(甚至只是 stdint.h)的编译器,是否有任何理由不使用 uint8_t 等固定宽度整数类型?

我知道的一个原因是,char在处理字符时使用 s 比使用 s更有意义(u)int8_t,正如这个问题中提到的那样。

但是如果你打算存储一个数字,你什么时候想使用一个你不知道它有多大的类型呢?即在什么情况下,您希望在unsigned short不知道它是 8、16 还是 32 位的情况下将数字存储在中,而不是使用 a uint16t

在此之后,使用固定宽度整数或使用普通整数类型并且从不假设任何内容并sizeof在需要知道它们使用多少字节的任何地方使用是否被认为是更好的做法?

4

6 回答 6

46

实际上,在不需要知道类型的确切大小的情况下存储数字是很常见的。我的程序中有很多数量我可以合理地假设不会超过 20 亿,或者强制它们不超过。但这并不意味着我需要一个精确的 32 位类型来存储它们,任何可以计数到至少 20 亿的类型对我来说都可以。

如果您尝试编写非常可移植的代码,则必须记住,固定宽度类型都是可选的。

在 C99 实现中, whereCHAR_BIT大于8no int8_t。该标准禁止它存在,因为它必须具有填充位,并且intN_t类型被定义为没有填充位(7.18.1.1/1)。uint8_t因此也被禁止,因为(谢谢,ouah)不允许定义uint8_t没有int8_t.

因此,在非常可移植的代码中,如果您需要一个能够保存高达 127 值的有符号类型,那么您应该使用、signed char或根据您是否要要求编译器制作它:intint_least8_tint_fast8_t

  • 在 C89 (signed charint)中工作
  • 避免在算术表达式中出现令人惊讶的整数提升 ( int)
  • 小(int_least8_tsigned char
  • 快(int_fast8_tint

对于高达 255 的无符号类型也是如此,带有unsigned char、和.unsigned intuint_least8_tuint_fast8_t

如果您需要在非常可移植的代码中进行模 256 运算,那么您可以自己取模、屏蔽位或使用位域玩游戏。

实际上,大多数人从不需要编写可移植的代码。目前CHAR_BIT > 8只出现在专用硬件上,您的通用代码不会在它上面使用。当然,这在未来可能会改变,但如果确实如此,我怀疑有太多代码对 Posix 和/或 Windows(两者都保证CHAR_BIT == 8)做出假设,那么处理代码的不可移植性将只是其中的一小部分将代码移植到该新平台的巨大努力。任何这样的实现都可能不得不担心如何连接到互联网(它处理八位字节),早在它担心如何让你的代码启动和运行之前:-)

如果您无论如何都假设,CHAR_BIT == 8那么我认为(u)int8_t除了您希望代码在 C89 中工作之外,没有任何特别的理由可以避免。stdint.h即使在 C89 中,为特定实现找到或编写一个版本也不是那么难。但是,如果您可以轻松地将代码编写为只要求类型可以保持255,而不是要求它不能保持256,那么您不妨避免对CHAR_BIT == 8.

于 2012-11-16T09:38:08.683 回答
17

尚未提及的一个问题是,虽然使用固定大小的整数类型意味着如果编译器对intlong等使用不同的大小,变量的大小不会改变,但它不一定保证即使定义了大小,该代码在具有各种整数大小的机器上的行为也会相同。

例如,给定声明uint32_t i;,表达式(i-1) > 5wheni为零时的行为将根据 auint32_t是否小于而有所不同int。在例如int64 位(uint32_t类似于long short)的系统上,变量i将被提升为int; 减法和比较将按有符号执行(-1 小于 5)。在int32 位的系统上,减法和比较将被执行为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不会]?或者,就此而言,编译器制造商很难保证任何整数类型表达式的中间结果都可以适合最大的有符号类型并且不涉及非常量的右移,将产生相同的结果结果好像所有计算都是在该类型上执行的?int对我来说,在32 位的机器上似乎很恶心:

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

a和的每个清除一位,但清除;c的前 33 位 b大多数编译器不会暗示第二个表达式有什么“不同”。

于 2012-11-16T20:49:29.663 回答
7

确实,标准整数类型的宽度可能会从一个平台更改为另一个平台,但不会更改其最小宽度

例如,C 标准规定 anint至少是16-bit并且 along至少是32-bit宽的。

如果您在存储对象时没有一些大小限制,您可以将其用于实现。例如,如果您的最大有符号值适合 a16-bit您可以只使用int. 然后,您让实现最终确定int实现所针对的体系结构的自然宽度。

于 2012-11-16T09:38:10.460 回答
4

代码应该向普通读者(和程序员他/她自己)揭示什么是重要的。它只是一些整数还是无符号整数,甚至是有符号整数。大小也是如此。某些变量默认为 16 位对算法来说真的很重要吗?或者这只是不必要的微观管理和失败的优化尝试?

这就是使编程成为一门艺术的原因——展示什么是重要的。

于 2012-11-16T09:48:34.397 回答
4

当您对宽度做出假设时,您应该只使用固定宽度类型。

uint8_t并且unsigned char在大多数平台上都相同,但并非在所有平台上都相同。使用uint8_t强调你假设一个 8 位架构char并且不会在其他架构上编译的事实,所以这是一个特性。

typedef否则我会使用诸如size_t,之类的“语义” uintptr_tptrdiff_t因为它们更好地反映了您对数据的想法。我几乎从不直接使用基本类型,int只用于错误返回,而且我不记得曾经使用过short.

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

于 2012-11-16T09:40:55.513 回答
3

人们想要使用的原因有很多,我们将它们称为语义类型,例如intchar固定宽度类型,例如uint8_t

匹配现有 API

标准 C 库char*随处使用。为什么在与该 API 交谈时使用不同的类型来混淆用户(并引入可能的错误?)?

类似地,printf()格式字符串是根据这些语义类型定义的。如果要打印固定大小的类型,则需要使用诸如PRIu64etc.之类的宏stdint.h来获得正确的格式字符串,以便使用旧格式字符串打印固定大小的类型printf

速度

通常选择语义类型,以便它们最适合当前 CPU 的性能特征。它们可能会被撞到比您选择的尺寸稍大的尺寸,因为这是您 CPU 上的寄存器大小,并且会为您节省不必要的转换等。

现在,这是一个有争议的答案......这是最初的意图,但由于stdint早期的 C/C++ 中不可用,许多平台(如 32 位 Windows 或 macOS X)只是保证大小intlong. 因此,在 64 位移动期间,其中一些大小保持不变(导致有趣的新类型,例如long long,等等)。这就是我们得到leastandfast类型的原因。

代码可移植性

语义类型在 64 位平台上可能比在 32 位平台上更大(例如,允许数组索引填满所有内存)。因此,如果您在不同的平台上运行,使用语义类型(根据我的定义将包括size_t可用的)而不是固定的意味着您正在利用更好的硬件而不是添加任意限制。

当然,这只会使您的算法具有可移植性。如果您需要将数据序列化为字节并在不同平台之间进行交换,这可能会使您的代码可移植,但不会使您的网络数据包或输出文件。因此,在这种情况下,您实际上希望坚持使用固定类型,以便数据保持可移植性,但代价是您的代码运行速度非常慢或无法在某些平台上编译。

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

于 2020-02-15T11:21:41.360 回答