C 编译器是否可以在将有符号转换为无符号时更改位表示形式


是否可以显式

强制转换,例如,int32_tuint32_t,以更改值的位表示?

例如,假设我有以下联合:

typedef union {
    int32_t signed_val;
    uint32_t unsigned_val;
} signed_unsigned_t;

规范是否保证这些代码段具有相同的行为?

uint32_t reinterpret_signed_as_unsigned(int32_t input) {
    return (uint32_t) input;
}

uint32_t reinterpret_signed_as_unsigned(int32_t input) {
    signed_unsigned_t converter;
    converter.signed_val = input;
    return converter.unsigned_val;
}

我在这里考虑 C99。我见过一些类似的问题,但它们似乎都在讨论C++,而不是C。

如果

可以找到具有符号量级或一补码有符号表示的机器,则将有符号整数类型转换为相同宽度的无符号整数类型可以更改表示形式。但是int32_tuint32_t的类型保证是二的补码表示,因此在这种特殊情况下,表示不能改变。

有符号整数

到无符号整数的转换在标准第 6.3.1.3 节中有明确定义。相关算法为第二段:

    当具有整数
  1. 类型的值转换为 _Bool 以外的其他整数类型时,如果 该值可以用新类型表示,它保持不变。
  2. 否则,如果新类型是无符号的,则通过重复添加或 比新类型中可以表示的最大值多减 1 直到该值在新类型的范围内。

因此,结果实际上必须是,如果负数存储在 2 的补码中,逐位复制会产生什么结果。允许符合要求的实现使用符号量级或一补码;在这两种情况下,都必须修改负整数的表示形式以强制转换为无符号。


在评论中总结一个冗长而有趣的讨论:

  • 在OP中使用int32_tuint32_t的精确示例中,如果程序编译,表示必须相等,因为C99要求int32_tuint32_t的长度正好为32位,没有填充,并且要求int32_t使用2的补码表示。但是,它不要求存在这些类型;一个一补码实现不能定义int32_t,并且仍然符合。

  • 我对类型双关语的解释低于水平规则。 @R..向我们指出了2004年的缺陷报告,该报告似乎说类型双关要么可以,要么触发陷阱,这更接近实现定义的行为而不是未定义的行为。另一方面,该DR的建议解决方案似乎不在C11文档中,其中说(6.2.6.1(5)):

某些对象表示

形式不需要表示对象类型的值。如果对象的存储值具有这样的表示形式,并且由没有字符类型的左值表达式读取,则行为是未定义的。

在我看来,这似乎是在说,如果其中一个参与类型具有陷阱表示,则类型双关是未定义的行为(因此,如果读取类型没有陷阱表示,则不是未定义的行为)。另一方面,不需要任何类型具有陷阱表示形式,并且只有少数类型被禁止具有陷阱表示:charunion类型 - 但不是联合类型的成员--,以及实现的任何[u]int*K_t类型。

我之前关于类型双关语的声明如下:


存储双关联合具有未定义的行为。但是,在不调用 lagartos voladores 的情况下,如果某个值存储为无符号然后按符号访问,则在某种程度上可以预期符号量级或一补码机器可能会引发硬件异常。

一补码和符号幅度都有两种可能的0表示,一个表示每个流行的符号位。带有负号位"负零"的那个允许为"陷阱值";因此,将值(即使只是复制它)作为有符号整数访问可能会触发陷阱。

尽管 C 编译器有权抑制陷阱,例如通过使用 memcpy 或无符号操作码复制值,但它不太可能这样做,因为这会让程序员感到惊讶,因为对于知道她的程序在捕获负零的机器上运行并且期望陷阱在非法值的情况下触发的程序员来说,这将是令人惊讶的。

在您提到的特定情况下,从 int32_tuint32_t 的转换 ,位表示将是相同的。

该标准特别要求intN_t是"宽度为N,没有填充位和二进制补码表示的有符号整数类型"。此外,相应的有符号和无符号类型必须对其共享范围内的值具有相同的表示形式:

有符号整数类型的有效(非陷阱)对象表示形式 其中符号位为零是 对应的无符号类型,并应表示相同的值。

有一个非常小的可能漏洞:原则上,一个实现可以,例如,int32_t 一个 typedef 表示intuint32_t 一个 typedef 表示unsigned long,其中 int and long 都是 32 位,但字节顺序不同。但这只会发生在故意反常的实施中。 更正:这对于符合要求的实现是不可能的。 int32_tuint32_t 必须表示相应的有符号和无符号类型。

上述内容仅适用于您碰巧为您的示例选择了int32_tuint32_t,并且标准对它们的表示进行了非常具体的限制。(如果实现不能满足这些限制,那么它根本不会定义int32_tuint32_t

但是,更一般地说,允许有符号类型具有以下三种表示形式之一:

    符号
  • 和幅度,其中将符号位设置为 1 将否定数字;
  • 二的补码,其中否定等价于按位补码,后跟加 1;和
  • 一个补码,其中否定等价于按位补码。

绝大多数现代系统使用二进制补码(并且没有填充位)。在此类系统上,具有相同大小类型的有符号到无符号转换通常不会更改位表示形式。(类型转换的语义是根据值定义的,但设计为方便 two 的补码系统。

但是对于使用符号和量级或补码的系统,有符号到无符号的转换必须保留值,这意味着负值的转换必须改变表示。

如果值同时在有符号和无符号类型的范围内,则转换时值和表示形式都不会更改。

否则,仅当类型的负值的实现表示为二进制补码时,才允许有符号到无符号转换保留位表示形式。对于补码或符号量级,它的转换必须改变表示。另一个方向的转换是实现定义的,因此它可能会也可能不会改变表示形式。

最新更新