【问题标题】:Is there any reason not to use fixed width integer types (e.g. uint8_t)?有什么理由不使用固定宽度的整数类型(例如 uint8_t)?
【发布时间】:2012-11-16 09:13:02
【问题描述】:

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

我知道的一个原因是在处理字符时使用chars 比使用(u)int8_ts 更有意义,正如this question 中提到的那样。

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

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

【问题讨论】:

标签: c integer byte sizeof c99


【解决方案1】:

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

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

CHAR_BIT 大于8 的C99 实现中,没有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的依赖。

【讨论】:

  • uint8_t for (CHAR_BIT>8) 的省略是一种觉醒。从来不知道。
  • @WhozCraig:其实我已经修改过了。我刚刚检查了标准,禁止填充位的文本是intN_t而不是uintN_t。但是那段说如果有一个类型具有正确的特征,那么uintN_t 必须被定义,说它只有 需要 如果有一个 8 位无符号类型没有填充(没有填充) t)。所以我说的有点不对,我认为如果CHAR_BIT > 8 那么int8_t 是禁止的,但uint8_t 仍然是可选的。
  • @SteveJessop int8_t 被禁止,但 uint8_t 仍然是可选的。 但是如果定义了uint8_t,那么int8_t 也需要根据C99, 7.18.1p1 定义.
  • @mux: 好吧,uint8_t 一个 8 位整数,所以如果它存在,那么实现确实提供了一个 8 位整数。很抱歉我因为改变我的故事而引起的任何混乱,因为它何时被允许存在。相反,CHAR_BIT > 8 的实现可以提供一个 8 位整数作为扩展类型,它不仅不允许将其称为 uint8_t
  • @eush77:实际上int 仅保证 C 标准至少有 16 位。 long 至少 32 岁。但我不明白你在说什么。如果我的数量不能超过 20 亿,并且有符号类型保证至少为 32 位,那么我可以可靠地使用它。只要我不进行按位运算(有时甚至是这样),无论它是 32 还是实际更大,它都是正确的。
【解决方案2】:

尚未提及的一个问题是,虽然使用固定大小的整数类型意味着如果编译器对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 执行(减法将产生一个非常大的数字,大于五)。

我不知道有多少代码依赖于这样一个事实,即涉及无符号类型的表达式的中间结果即使在没有类型转换的情况下也需要包装(恕我直言,如果需要包装行为,程序员应该包含一个类型转换) (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;

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

【讨论】:

    【解决方案3】:

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

    例如,C 标准规定int 至少为16-bitlong 至少为32-bit 宽。

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

    【讨论】:

      【解决方案4】:

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

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

      【讨论】:

        【解决方案5】:

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

        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

        【讨论】:

        • 我认为在定义了uint8_t 的平台上,uint8_t 始终为unsigned char(或char 假设char 未签名)。
        • char 大于 8 位的系统不会定义 uint8_t。而uint8_t 可以是unsigned char 或普通的char(如果普通的char 未签名且CHAR_BIT==8)。
        • @KeithThompson,确切地说(对于第一部分)。所以不会定义uint8_t 的平台不会编译这样的代码,这就是我所说的。如果代码中实际上存在需要CHAR_BIT8 的假设,我认为这是一个特性。对于第二部分,您是否有足够疯狂的架构示例,将uint8_t 类型定义为char
        • @JensGustedt:对,你已经说过了;我读得不够仔细。我不知道任何将uint8_t 定义为普通char 的实现(或者int8_t,如果已签名普通char)。使用unsigned charsigned char 更简单,但使用普通的char 是完全合法的。
        • @KeithThompson,实际上不,这不合法,请查看我的编辑。
        【解决方案6】:

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

        匹配现有 API

        标准 C 库在任何地方都使用 char*。为什么在与该 API 对话时使用不同的类型来混淆用户(并引入可能的错误?)?

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

        速度

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

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

        代码可移植性

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

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

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-02-05
          • 2023-01-12
          • 2015-03-26
          相关资源
          最近更新 更多