【问题标题】:Byte precision pointer arithmetic in C when sizeof(char) != 1当 sizeof(char) != 1 时 C 中的字节精度指针算术
【发布时间】:2010-12-24 07:28:03
【问题描述】:

如何以单字节精度便携地执行指针运算?

请记住:

  • char 在所有平台上都不是 1 字节
  • sizeof(void) == 1 仅在 GCC 中作为扩展提供
  • 虽然某些平台可能有指针 deref 指针对齐限制,但算术可能仍需要比最小基本 POD 类型的大小更精细的粒度

【问题讨论】:

  • 我很好奇 - 哪个不起眼的平台有一个字符而不是一个字节?听起来问题的整个前提是过早的可移植性;)
  • 许多 DSP 芯片都有 > 8 位 char 类型。但根据定义,sizeof(char) == 1 无处不在。我想我也听说过 9 位 char 系统。
  • 至少有一个嵌入式平台,sizeof(char)sizeof(int) 相同(均为 1),这意味着这两种类型都是相同大小的 32 位整数。不过,我不确定该平台上 machine 字节的大小。
  • 我听说过 11 位和 32 位字符。
  • Anacrolix,无关紧要。 char 中的位由 CHAR_BIT 给出。不改变 sizeof(char)。

标签: c compiler-construction portability void-pointers pointer-arithmetic


【解决方案1】:

C99 标准定义了一个字节长的 uint8_t。如果编译器不支持这种类型,你可以使用 typedef 来定义它。当然,您需要不同的定义,具体取决于平台和/或编译器。将所有内容捆绑在一个头文件中,并在任何地方使用它。

【讨论】:

  • "如果编译器不支持这种类型,你可以使用 typedef 来定义它"。其实你不能。如果编译器具有提供 uint8_t 行为的类型,则它必须在 stdint.h 中定义 uint8_t。因此,如果它没有定义它,那么您就无法为自己定义任何具有正确语义的东西。但是,您可能能够接近,例如,如果实现具有带有填充位的 8 位类型。假设是 C99 编译器。
  • 非 C99 编译器呢?通常是 'typedef unsigned char uint8_t;'会给你一个字节宽的类型。 uint8_t 语义是否比 8 位数据类型更重要?
  • C89 编译器根本不一定有 stdint.h,所以你不能假设如果他们可以实现 uint8_t,那么他们就会。实际上,我认为我错了,对 uint8_t 没有任何额外的要求,所以你永远不会“接近但不完全在那里”。具有额外要求的是 int8_t:必须是 2 的补码并且没有填充位。就我而言,这是一个糟糕的例子,我会再试一次:例如,如果你的编译器有一个 16 位字符,那么可能根本就没有任何 8 位类型,因此没有任何东西可以用作 uint8_t。依赖它的代码不是完全可移植的。
【解决方案2】:

sizeof(char) 被 C 标准保证为 1。即使char 使用 9 位或更多。

所以你可以这样做:

type *pt;
unsigned char *pc = (unsigned char *)pt;

并使用pc 进行算术运算。 尽管使用上述转换将pc 分配给pt 是C 标准未定义的行为。

如果char 的宽度超过 8 位,则不能在可移植 (ANSI/ISO) C 中进行字节精度指针运算。这里,byte 是指 8 位.这是因为基本类型本身大于 8 位。

【讨论】:

  • 根据标准实际上完全允许检查任何对象,就好像它是charunsigned charsigned char 的数组一样。为了确保允许这样做,我们做出了一些保证——比如char 可能没有填充位。
  • @caf:只有unsigned char 类型保证没有填充位。 signed char 类型可以有填充位(和陷阱表示)。语言规范允许将对象重新解释为signed char 的数组,但您有责任确保不会遇到signed char 的陷阱表示。如果您真的想确保安全地重新解释,请始终使用 unsigned char 的数组。
  • 你当然是对的。在声明未定义的行为之前,我应该更加小心。感谢您纠正我。
  • @AndreyT:我认为 caf 是在谈论 指针:我的标准副本说(第 3.2.2.2 节):指向非限定类型的指针可以转换为指向该类型的限定版本的指针;存储在原始指针和转换后的指针中的值应该比较相等。
  • @Alok:不,很明显,caf 正在谈论将任何对象重新解释为 [signed/unsigned] char 对象的数组。
【解决方案3】:

将指针转换为uintptr_t。这将是一个指针大小的无符号整数。现在对其进行算术运算,然后将结果转换回您要取消引用的类型的指针。

(请注意,intptr_t 已签名,这通常不是您想要的!坚持uintptr_t 会更安全,除非您有充分的理由不这样做!)

【讨论】:

【解决方案4】:

sizeof(char) 总是返回 1,in both Cand C++char 总是一个字节长。

【讨论】:

  • 第一个链接现在失效了。 :)
【解决方案5】:

根据标准char 是最小的可寻址数据块。您只是无法更精确地处理 - 您需要手动进行打包/拆包。

【讨论】:

    【解决方案6】:

    您的假设有缺陷 - sizeof(char)定义 为 1。

    来自C99 standard (TC3),在第 6.5.3.4 节(“sizeof 运算符”)中:

    (第 2 段)

    sizeof 运算符产生大小 (以字节为单位)其操作数,这可能 是一个表达式或 带括号的类型名称。

    (第 3 段)

    当应用于具有 输入 char、unsigned char 或 signed char,(或合格的版本 其中)结果为 1。

    当将这些放在一起时,很明显在 C 中,无论 char 是什么大小,该大小都是一个“字节”(即使在某些给定平台上超过 8 位)。

    因此,char 是最小的可寻址类型。如果您需要以小于char 的单位寻址,您唯一的选择是一次读取char,并使用按位运算符屏蔽您想要的char 部分。

    【讨论】:

    • OP 没有说 'sizeof(char)' 可以不同于 1。我相信,OP 特意避免了这种措辞,以使人们理解“不是所有平台上的 1 个字节”的意思“不是 1 个 机器 字节”。这是完全可能的,即使在语言级别 sizeof(char) 仍将始终为 1。
    • @caf 如果您可以提供 C99 标准的这些部分的链接,我会接受这个作为答案,谢谢
    • AndreyT:看问题标题。
    • @Anacrolix,他提供了一个“链接”:第 6.5.3.4 节。因此,您正在尝试在没有可用的当前 ANSI C 标准(甚至没有免费草案)的情况下用 C 编写可移植程序?
    • 没有这个链接是给别人的,作为参考,它将使它成为一个更好的答案。
    【解决方案7】:

    我不明白你想说sizeof(void) 在 GCC 中是 1 是什么意思。虽然类型 char 理论上可能包含超过 1 个底层机器字节,但在 C 语言中,sizeof(char) 是 1,并且始终是 1。换句话说,从 C 语言的角度来看,char 始终是 1"字节"(C 字节,不是机器字节)。一旦你理解了这一点,你也会明白sizeof(void) 在 GCC 中是 1 对你没有任何帮助。在 GCC 中,void * 指针上的指针运算与 char * 指针上的指针运算完全相同,这意味着如果在某些平台上 char * 对您不起作用,那么 void * 将不起作用也适合你。

    如果在某些平台上char 对象由多个机器字节组成,访问比完整的char 对象更小的内存单元的唯一方法是使用按位运算来“提取”和“修改”所需部分一个完整的char 对象。 C 语言无法直接处理小于char 的任何内容。再一次,char 始终是 C 字节。

    【讨论】:

      猜你喜欢
      • 2012-12-27
      • 2013-06-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-08-31
      • 2010-10-20
      • 1970-01-01
      • 2012-07-27
      相关资源
      最近更新 更多