【问题标题】:Does accessing an int with a char * potentially have undefined behavior?使用 char * 访问 int 是否可能具有未定义的行为?
【发布时间】:2018-02-01 19:14:43
【问题描述】:

以下用于测试字节顺序的代码预计具有实现定义的行为:

int is_little_endian(void) {
    int x = 1;
    char *p = (char*)&x;
    return *p == 1;
}

但它是否可能在故意设计的架构上具有未定义的行为?例如,int 表示的第一个字节,其值为 1(或另一个精心选择的值)是否可以是 char 类型的陷阱值?

如 cmets 中所述,unsigned char 类型不会有此问题,因为它不能有陷阱值,但此问题特别涉及 char 类型。

【问题讨论】:

  • 你可以使用unsigned char,我相信它不会有任何陷阱值吗?
  • 注意unsigned char不能有陷阱值。

标签: c casting language-lawyer undefined-behavior


【解决方案1】:

根据 C 2018 6.2.5 15,char 的行为类似于 signed charunsigned char。假设它是signed char。 6.2.6.2 2 讨论有符号整数类型,包括signed char。在本段的末尾,它说:

这些[符号和幅度二的补码一个的补码]中的哪一个适用是实现定义的,正如是否符号位为 1 且所有值位为零(对于前两位)或符号位和所有值位为 1(对于一个补码)的值是陷阱表示或正常值。

因此,这一段允许signed char 有一个陷阱表示。但是,标准中说访问陷阱表示可能具有未定义行为的段落 6.2.6.1 5 明确排除了字符类型:

某些对象表示不需要表示对象类型的值。如果对象的存储值具有这样的表示形式并且由不具有字符类型的左值表达式读取,则行为未定义。如果这种表示是由通过不具有字符类型的左值表达式修改对象的全部或任何部分的副作用产生的,则行为未定义。这种表示称为陷阱表示。

因此,尽管char 可能有陷阱表示,但我们没有理由不能访问它。那么问题来了,如果我们在表达式中使用值会发生什么?如果char 具有陷阱表示,则它不表示值。因此,尝试将其与 *p == 1 中的 1 进行比较似乎没有明确的行为。

对于任何正常的 C 实现,int 中的特定值 1 不会导致 char 中的陷阱表示,因为 1 将位于某个字节的“最右边”(最低值)位中int,并且没有正常的 C 实现将 char 的符号位放在该位置的位中。但是,C 标准显然不禁止这种安排,因此,理论上,值为 1 的int 可能在其一个字节中使用位 00000001 进行编码,并且这些位可能是char 的陷阱表示。

【讨论】:

  • 你成功了!您还在 cmets 中指出 unsigned charsigned char 都不能有填充位,这将陷阱值的可能性限制在您引用的段落中。这种精确度属于您的答案。
【解决方案2】:

我不认为标准会禁止signed char 使用符号幅度或一个补码格式的实现,并在尝试加载表示“负零”的位模式时陷入困境。它也不要求此类实现必须使char 无符号。可以设计一种架构,您的代码可以在该架构上具有任意行为。不过,还有一些更重要的事情需要注意:

  1. 无法保证char 中的位与int 中的位映射顺序相同。如果位没有按顺序映射,代码将不会启动到 UB-land,但结果不会很有意义。

  2. 据我所知,每个非人为的符合 C99 的实现都使用二进制补码格式;我认为有人会不这样做是值得怀疑的。

  3. 如果实现使 char 成为可表示值少于位模式的类型,那将是愚蠢的。

  4. 只要存在一些可以按照标准定义的方式处理的源文本,就可以设计出一种符合标准的实现,该实现几乎可以对任何源文本执行任何操作。

可以设计一个符合符号幅度的实现,其中整数值 1 将具有一个位模式,该模式将编码带符号的 char 值“负零”,并且会在尝试加载它时陷入困境。甚至可以设计一个符合要求的补码实现(在“int”类型上有很多填充位,所有这些在存储值“1”时都被设置)。鉴于人们可以设计一个符合要求的实现,该实现使用 One Program 规则来证明对上述源文本做任何它喜欢的事情,而不管它使用什么整数格式,但是,我认为奇怪的 char 类型的可能性不应该真的放心吧。

注意,顺便说一句,标准没有努力禁止愚蠢的实现;可以通过添加语言来改进它,要求 char 必须是没有陷阱表示的二进制补码类型或无符号类型,并且要么对 signed char 强制要求相同,要么明确表示不需要。如果它识别出不能支持像unsigned long long 这样的类型的实现类别,它也可能会得到改进[这将是 36 位补码系统的主要绊脚石,并且可能是不符合 C99 的原因此类平台存在实现]。

【讨论】:

  • 感谢您的详细解答。这不必担心,我只是在寻找通过识别会使非 2 的补码表示不符合的细微约束来简化标准的方法。
  • @chqrlie:标准的问题不是它太复杂,也不是太简单,而是它没有认识到应该提供一些特性和保证大多数实现,但不一定是全部,并且应该有一种简单的方法,通过该方法,那些没有被编程以适应“古怪”实现的程序可以拒绝在它们上运行,而不必依赖“未定义的行为”。
  • 您关注非 2 的补码行为,其中负零可能是 char 的陷阱值,但我在标准中看不到任何阻止 char 即使在 2 的补码上具有填充位的内容架构(常识除外)
  • @chqrlie: char 不能有填充位,因为它与signed charunsigned char 具有相同的行为,并且signed char 不能有6.2.6.2 中明确规定的填充位 2 , 而unsigned char 不能有填充位,因为它的所有位都用于值。
  • 字符类型被排除在读取陷阱表示时的未定义行为规则之外,根据 C 2018 6.2.6.1 5:“某些对象表示不需要表示对象类型的值。如果对象的存储值具有这样的表示形式并且被不具有字符类型的左值表达式读取,则该行为是未定义的……”
【解决方案3】:

我从标准中找到了一个引用,证明没有对象表示是unsigned char 的陷阱值:

6.2.6.2 整数类型

1 对于 unsigned char 以外的无符号整数类型, 对象的位 表示应分为两组:值位和填充位(需要 不是后者)。如果有 N 个值位,每个位应代表不同的 1 和 2N-1 之间的 2 的幂,因此该类型的物体应能够 使用纯二进制表示表示从 0 到 2N - 1 的值;这应该是 称为值表示。任何填充位的值都是未指定的。53)

前面说unsigned char 不能有任何填充位。

以下脚注说明填充位可用于陷阱表示。

53) 填充位的某些组合可能会产生陷阱表示,例如,如果一个填充 位是奇偶校验位。无论如何,对有效值的算术运算都不会产生陷阱 表示不是作为异常条件(如溢出)的一部分,并且这不会发生 无符号类型。填充位的所有其他组合都是替代对象表示 值位指定的值。

所以我猜答案是 char 不能保证没有任何陷阱值,但 unsigned char 是。

【讨论】:

  • 人们为什么要投票?该问题专门询问char 而不是unsigned char,但该答案的大部分内容涵盖unsigned char 和填充位(char 中不存在)并且仅在最后一句中“猜测”@987654329如果不支持这种猜测,@ 不能保证没有任何陷阱表示。
  • @EricPostpischil 该标准不保证char 不能有填充位。在拥有它们的实现中,很可能有一个陷阱值。没有猜测。答案提供unsigned char 作为选项。
  • 每 6.2.5 5,signed char 占用与char 相同的存储量。根据 6.2.5 15,charsigned charunsigned char 具有相同的范围、表示和行为。根据 6.2.6.2 2,signed char 不应有任何填充位。根据 6.2.6.1,存储在 unsigned char 类型的对象中的值使用纯二进制表示。脚注 49 清楚地说明了这个短语意味着 unsigned char 可以表示从 0 到 2**CHAR_BIT-1 的所有值,因此使用了所有位。没有填充位。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-12-30
相关资源
最近更新 更多