【问题标题】:Is the size of every C data type guaranteed to be an integer multiple of bytes?每个 C 数据类型的大小是否保证是字节的整数倍?
【发布时间】:2016-10-11 12:37:03
【问题描述】:

我知道 sizeof(char) 将始终为 1,并且以字节为单位,并且一个字节可以是任意位数(我相信任意位数大于或等于 8,但不是积极的)。

我还经常看到提到如何根据大小之间的关系来指定 C 数据类型大小的参考资料,例如“sizeof(int)

我的问题基本上是:在一个字节为 8 位且 int 为 39 位(或其他不能被 CHAR_BIT 整除的值)的系统上,“sizeof(int)”的计算结果是什么。

我的猜测是 sizeof() 返回存储类型所需的最小字节数,因此它会四舍五入到下一个字节。所以在我的 39 位 int 示例中,sizeof(int) 的结果将是 5。

这是正确的吗?

此外,是否有一种简单的方法可以确定特定类型可以容纳的位数,该方法是 100% 可移植且不需要包含任何标头的?这更多是为了学习体验而不是实际应用。我只会在实践中使用 stdint 类型。我在想可能是声明变量并将其初始化为〜0,然后循环并左移直到它为零。

谢谢!

【问题讨论】:

  • 答案在Wikipedia article about sizeof的前两行
  • int 在那种情况下不能是 39 位。您可以应用sizeof 的任何类型都将包含整数个字节。 (位域可以有小数大小,但它们不是真正的类型,在它们上使用sizeof 是错误的。)
  • @EugeneSh。维基百科不具有权威性。
  • @sleeptightpupper 是这样吗?
  • @EugeneSh。我从来没有说过是。我只是说你不能链接到维基百科并说事实上的“答案就在里面”。唯一的权威来源是 C 标准。

标签: c c11 c89


【解决方案1】:

人们经常无法理解的是,在 C 语言中,sizeof 和“宽度”之间有明显的区别。

“宽度”更多地是关于二进制表示、范围、溢出/环绕行为。你说一个无符号整数类型是 16 位宽那么你的意思是它在 65535 处环绕。

但是sizeof 只关心存储。因此,sizeof(T[n])==sizeof(T)*n 是通过允许 sizeof 包含填充来维护的。

因此,试图在 sizeof 一个类型和一个类型的算术行为之间找到联系是没有意义的:一个类型可以有一定的范围,但可以占用它想要的任何存储空间。

为了回答您的问题(“如果 8 位字符机器上的 39 位 int 怎么办?”)我想以 TI C6400+ 为例,因为它有一个 40 位 long 和8 位字符,非常接近。

TI C6400+ 是字节可寻址机器,因此它必须将 8 位字节定义为 char

它还有一个 40 位整数类型,因为 ALU 可以对 40 位整数进行运算,他们将其定义为long

你会认为sizeof(long) 应该是 5,对吧?

嗯,它可以,但是这个 CPU 也不能很好地支持 unaligned-load,所以出于性能原因,这个 long 类型默认对齐到 8 字节边界而不是 5 字节,然后每个 long有 3 个字节的填充(在内存和寄存器级别,因为它在 CPU 中也需要一对 GPR),那么自然sizeof(long) 变为 8。

有趣的是,C6400+ C 实现还提供了 long longsizeof(long long) 也是 8。但这是真正的 64 位宽类型,并且具有完整的 64 位范围而不是 40 位。

更新

回到“39 位”的情况。

由于 6.2.8.1 要求所有完整类型的对齐是“字节”的整数倍,因此如果 CHAR_BIT 为 8,则必须将 39 位整数填充到至少 40 位或更大,所以 sizeof这种类型必须是大于或等于 5 的整数。

【讨论】:

  • 我意识到了区别,这就是为什么我想知道 sizeof() 是否会四舍五入。我依赖的唯一保证是“char”是最小的类型并且它包含 CHAR_BIT 位。所有其他类型的 storage 大小是字节的倍数,因此包含 n * CHAR_BIT 位(根据 6.2.6.1)。但是,正如您的示例所证明的那样,这不一定反映该类型的算术能力。我的问题实际上是关于“n”是否必须是整数,这就是为什么我在示例中选择 39 位 int 而不是可以被 CHAR_BIT 整除的数字。
  • @JakeMoroni 那么这更多的是关于对齐而不是大小。 6.2.8.1 说对齐是“整数个字节”。在 C 中“字节”是 char 那么是的,它必须是可整除的,或者填充到 CHAR_BIT 的倍数。
【解决方案2】:

Chapter and verse:

6.2.6 类型的表示

6.2.6.1 常规

  1. 未指定所有类型的表示,除非本子条款中另有说明。
  2. 除了位域,对象由一个或多个字节的连续序列组成, 明确指定的数量、顺序和编码,或 实现定义。
  3. 存储在无符号位域和unsigned char 类型对象中的值应为 使用纯二进制表示法表示。49)
  4. 存储在任何其他对象类型的非位域对象中的值由<em>n</em> × CHAR_BIT 组成 位,其中<em>n</em> 是该类型对象的大小,以字节为单位。 该值可以复制到 unsigned char [n] 类型的对象(例如,memcpy);结果的字节集是 称为值的对象表示。存储在位域中的值由<em>m</em> 位组成, 其中<em>m</em> 是为位域指定的大小。对象表示是<em>m</em>的集合 位字段包含在保存它的可寻址存储单元中的位。两个值(其他 比 NaNs) 具有相同的对象表示比较相等,但比较的值 equal 可能有不同的对象表示。

49) 整数的位置表示,使用二进制数字 0 和 1,其中的值 由连续的位表示是相加的,从 1 开始,乘以连续的整数 2 的幂,但可能位置最高的位除外。 (改编自American National 信息处理系统字典。)一个字节包含CHAR_BIT位,以及 键入unsigned char 范围从02^CHAR_BIT − 1

我的问题基本上是:在一个字节为 8 位且 int 为 39 位(或其他不能被 CHAR_BIT 整除的值)的系统上,“sizeof(int)”的计算结果是什么。

实现必须将CHAR_BIT 大小的存储单元映射到奇数大小的单词上,这样才能满足上述要求,这可能会导致显着的性能损失。一个 39 位字最多可以容纳四个 8 位或 9 位存储单元,因此sizeof (int)可能计算为 4。

或者,实现者可以简单地认为不值得麻烦并将CHAR_BIT设置为39; 所有内容,包括单个字符,占用一个或多个完整的单词,根据类型最多保​​留 31 位未使用。

过去曾有过这类事情的真实例子。旧的 DEC PDP 之一(我想说 PDP-8,也许是 PDP-11?)使用 36 位字和 7 位 ASCII 作为字符值; 5 个字符可以存储在一个单词中,其中一位未使用。所有其他类型占据了一个完整的单词。如果实现将 CHAR_BIT 设置为 9,您可以将 CHAR_BIT 大小的存储单元干净地映射到 36 位字,但同样,如果 硬件 需要 5 个字符,这可能会导致显着的性能损失每个字。

【讨论】:

  • 感谢您引用标准。我只是对您的回答有疑问:> [...一个 39 位字最多可以容纳四个 8 位或 9 位存储单元,因此 sizeof (int) 可能评估为 4。] 如果 CHAR_BIT 为 8,那么这似乎违反了 6.2.6.1 下的 #4,因为它声明“该值可能被复制到 unsigned char [n] 类型的对象中(例如,通过 memcpy)”。 (“n”是 sizeof(type)“的结果。在这种情况下,如果“n”是 4,那么这将不起作用,因为您总共有 39 位要存储,但您的数组由 4*8 组成位。
  • 这是 PDP-6 和 PDP-10。但是 7 位字符只是一个约定;这些机器是字寻址的,但具有允许在一个字内使用任何位范围的操作码。正常地址为 18 位,因此其中两个适合一个字;位范围引用需要一个完整的字。一个词可以包含六个字母数字,允许六个字符的变量名适合一个词;更积极的压缩(不使用位范围)允许使用七字符不区分大小写的标识符。很难看出假设的 C 编译器是如何实现这种灵活性的。
  • @JakeMoroni:事先承认这样的事情可能不存在,想象一下允许您屏蔽和访问 39 位字的任意区域的架构和指令集,这样您就可以映射一个连续的 8 位“字节”序列,其中每五个“字节”左右将跨越一个字边界。逻辑“字节”地址不会与物理字地址对齐,这意味着性能会suck,但这就是我对这种实现的设想。在这种情况下,多字节类型的大小完全是任意的。
【解决方案3】:

每个 C 数据类型的大小都保证是字节的倍数吗?

是的。即使表示数据类型所需的位数小于CHAR_BIT,例如struct foo { unsigned int bar : 1};int16_tCHAR_BIT = 32 的系统上。

sizeof(char) = 1 (guaranteed)
CHAR_BIT >= 8 ( = 8 on posix compliant system)
sizeof(anything else) = k * sizeof(char) = k where k is a whole number

来自6.5.3.4 sizeof和_Alignof运算符

sizeof 运算符产生其操作数的大小(以字节为单位),即 可以是表达式或类型的括号名称。尺寸是 由操作数的类型决定。结果是一个整数。 [...]

让您更加困惑的是,在具有CHAR_BIT = 32 的机器上,sizeof(int16_t) 将为 1,sizeof(int32_t)不过)如果您使用malloc(sizeof(int16_t) * n) 而不是malloc(sizeof(int16_t[n])

【讨论】:

  • CHAR_BIT = 32sizeof(int16_t)不能为1,因为intN_t不能有填充位。它根本不会由 32 位字符实现提供。而且sizeof(int16_t) * nsizeof(int16_t[n])完全一样。
  • 您不应该使用位域作为示例,因为它与您的其余答案相矛盾。位域恰好占用您指定的位数。否则,它需要n * CHAR_BIT 位,其中n 是字节数。
  • 如果 CHAR_BIT == 32 则 int16_t 不存在
  • @sleeptightpupper 填充位出现在位域之后,以确保正确对齐。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-07-08
  • 2017-09-29
  • 2011-03-13
  • 2019-05-15
相关资源
最近更新 更多