假设我有以下 C 代码。

unsigned int u = 1234;
int i = -5678;

unsigned int result = u + i;

这里发生了什么隐式转换,这段代码对于所有值都是安全的 ui?(安全,从某种意义上说,即使 结果 在这个例子中将溢出到一些巨大的正数,我可以将其转换回一个 整数 并得到真实的结果。)

有帮助吗?

解决方案

简答

你的 i转换的 通过添加到无符号整数 UINT_MAX + 1, ,那么就会和无符号的值进行加法,得到一个很大的结果 result (取决于值 ui).

长答案

根据C99标准:

6.3.1.8 常用算术转换

  1. 如果两个操作数具有相同的类型,则不需要进一步转换。
  2. 否则,如果两个操作数都具有有符号整数类型或都具有无符号整数类型,则具有较小整数转换等级的类型的操作数将转换为具有较大等级的操作数的类型。
  3. 否则,如果无符号整数类型的操作数的等级大于或等于另一个操作数的类型的等级,则有符号整数类型的操作数将转换为无符号整数类型的操作数的类型。
  4. 否则,如果有符号整型操作数的类型可以表示无符号整型操作数的所有值,则将无符号整型操作数转换为有符号整型操作数的类型。
  5. 否则,两个操作数都转换为与有符号整数类型操作数的类型相对应的无符号整数类型。

就您而言,我们有一个无符号整数(u) 和有符号 int (i)。参考上面的 (3),由于两个操作数具有相同的等级,因此您的 i 将需要 转换的 为无符号整数。

6.3.1.3 有符号和无符号整数

  1. 当一个整数类型的值转换为_Bool以外的其他整数类型时,如果该值可以用新类型表示,则该值不变。
  2. 否则,如果新类型是无符号的,则通过重复加或减新类型可以表示的最大值1来转换该值,直到该值在新类型的范围内。
  3. 否则,新类型是有符号的,并且该值无法在其中表示;结果要么是实现定义的,要么引发实现定义的信号。

现在我们需要参考上面的(2)。你的 i 将通过添加转换为无符号值 UINT_MAX + 1. 。所以结果取决于如何 UINT_MAX 在您的实现中定义。它会很大,但不会溢出,因为:

6.2.5 (9)

涉及无符号操作数的计算永远不会溢出,因为无法由结果无符号整数类型表示的结果会按比结果类型可以表示的最大值大 1 的数字进行减模。

奖金:算术转换半WTF

#include <stdio.h>

int main(void)
{
  unsigned int plus_one = 1;
  int minus_one = -1;

  if(plus_one < minus_one)
    printf("1 < -1");
  else
    printf("boring");

  return 0;
}

您可以使用此链接在线尝试: https://repl.it/repls/QuickWhimsicalBytes

奖金:算术转换副作用

可以使用算术转换规则来获取值 UINT_MAX 通过将无符号值初始化为 -1, , IE:

unsigned int umax = -1; // umax set to UINT_MAX

由于上述转换规则,无论系统的带符号数字表示如何,都保证是可移植的。请参阅此问题以获取更多信息: 使用 -1 将所有位设置为 true 是否安全?

其他提示

从有符号到无符号的转换 不是 不一定只是复制或重新解释有符号值的表示。引用C标准(C99 6.3.1.3):

当具有整数类型的值转换为_bool以外的其他整数类型时,如果值可以用新类型表示,则它是不变的。

否则,如果新类型未签名,则通过反复添加或减去一个比新类型中可以表示的最大值多数来转换值,直到值在新类型的范围内为止。

否则,新类型是有符号的,并且该值无法在其中表示;结果是实现定义,或者升级实现定义的信号。

对于当今几乎通用的二进制补码表示形式,规则确实对应于重新解释这些位。但对于其他表示形式(符号和数值或补码),C 实现仍必须安排相同的结果,这意味着转换不能仅复制位。例如,(无符号)-1 == UINT_MAX,无论表示形式如何。

一般来说,C 中的转换被定义为对值进行操作,而不是对表示进行操作。

回答原来的问题:

unsigned int u = 1234;
int i = -5678;

unsigned int result = u + i;

i 的值被转换为 unsigned int,得到 UINT_MAX + 1 - 5678. 。然后将该值与无符号值 1234 相加,得到 UINT_MAX + 1 - 4444.

(与无符号溢出不同,有符号溢出会调用未定义的行为。环绕很常见,但 C 标准无法保证 —— 并且编译器优化可能会对做出无根据假设的代码造成严重破坏。)

参考 圣经:

  • 您的加法操作会导致 int 转换为无符号 int。
  • 假设二进制补码表示和大小相同的类型,位模式不会改变。
  • 从 unsigned int 到signed int 的转换取决于实现。(但现在它在大多数平台上的工作方式可能符合您的预期。)
  • 在组合不同大小的有符号和无符号的情况下,规则稍微复杂一些。

当一个无符号变量和一个有符号变量相加(或任何二元运算)时,两者都会隐式转换为无符号变量,在这种情况下会产生巨大的结果。

所以它是安全的,因为结果可能是巨大的和错误的,但它永远不会崩溃。

从有符号转换为无符号时,有两种可能性。最初为正数的数字保持(或解释为)相同的值。最初为负数的数字现在将被解释为更大的正数。

正如前面所回答的,您可以毫无问题地在签名和未签名之间来回转换。有符号整数的边界情况是 -1 (0xFFFFFFFF)。尝试对其进行加减,您会发现可以进行回溯并使其正确。

但是,如果您要来回转换,我强烈建议您命名变量,以便清楚它们的类型,例如:

int iValue, iResult;
unsigned int uValue, uResult;

如果在没有提示的情况下命名变量,那么很容易被更重要的问题分散注意力并忘记哪个变量是什么类型。您不想转换为无符号,然后将其用作数组索引。

这里发生了什么隐式转换,

i 将被转换为无符号整数。

这段代码对于 u 和 i 的所有值都是安全的吗?

在明确定义的意义上安全是的(参见 https://stackoverflow.com/a/50632/5083516 ).

这些规则通常以难以阅读的标准语言编写,但本质上无论在有符号整数中使用什么表示形式,无符号整数都将包含该数字的 2 补码表示形式。

加法、减法和乘法将正确地对这些数字进行运算,从而产生另一个无符号整数,其中包含表示“实际结果”的二进制补码数字。

除法和转换为更大的无符号整数类型将得到明确定义的结果,但这些结果不会是“实际结果”的 2 补码表示。

(安全,因为即使这个例子中的结果会溢出到某个巨大的正数,我也可以将其转换回 int 并获得真正的结果。)

虽然从有符号到无符号的转换是由标准定义的,但相反的是实现定义的,gcc 和 msvc 都定义了转换,这样当将存储在无符号整数中的 2 的补码转换回有符号整数时,您将获得“真实结果” 。我希望您只会在不使用 2 的补码作为有符号整数的晦涩系统上发现任何其他行为。

https://gcc.gnu.org/onlinedocs/gcc/Integers-implementation.html#Integers-implementation https://msdn.microsoft.com/en-us/library/0eex498h.aspx

可怕的答案很多

厄兹古尔·厄兹西塔克

当您从签名到未签名(反之亦然)时,该数字的内部表示不会改变。更改是编译器如何解释符号位。

这是完全错误的。

马茨·弗雷德里克森

当添加一个未签名和一个签名变量(或任何二进制操作)时,两者都会隐式转换为未签名,在这种情况下,这将导致巨大的结果。

这也是错误的。如果由于无符号类型中的填充位而具有相同的精度,则无符号整数可以提升为整数。

smh

您的添加操作会导致INT转换为未签名的INT。

错误的。也许会,也许不会。

从无符号int到签名的int的转换取决于实现。(但是,如今,它可能会按照您期望的方式工作。)

错误的。如果它导致溢出,则它是未定义的行为,或者该值被保留。

匿名的

i的值转换为未签名的int ...

错误的。取决于 int 相对于 unsigned int 的精度。

泰勒·普赖斯

如前所述,您可以在签名和未签名之间来回抛弃而不会出现问题。

错误的。尝试存储有符号整数范围之外的值会导致未定义的行为。

现在我终于可以回答这个问题了。

如果 int 的精度等于 unsigned int,则 u 将被提升为有符号 int,并且您将从表达式 (u+i) 中得到值 -4444。现在,如果你和我有其他值,你可能会得到溢出和未定义的行为,但有了这些确切的数字,你将得到 -4444 [1]. 。该值的类型为 int。但是您试图将该值存储到无符号 int 中,以便随后将其转换为无符号 int,结果最终的值将是 (UINT_MAX+1) - 4444。

如果 unsigned int 的精度大于 int 的精度,则signed int 将提升为 unsigned int,生成值 (UINT_MAX+1) - 5678,该值将添加到另一个 unsigned int 1234 中。如果 u 和 i 具有其他值,导致表达式超出范围 {0..UINT_MAX},则将添加或减去值 (UINT_MAX+1),直到结果落入范围 {0..UINT_MAX) 并不会发生未定义的行为。

什么是精度?

整数具有填充位、符号位和值位。无符号整数显然没有符号位。Unsigned char 进一步保证没有填充位。整数的值位数就是它的精度。

[陷阱]

如果存在填充位,则宏 sizeof 宏不能单独用于确定整数的精度。并且字节的大小不必是 C99 定义的八位字节(八位)。

[1] 溢出可能发生在两点之一。在加法之前(在提升期间) - 当您有一个太大而无法放入 int 的无符号 int 时。即使unsigned int在int的范围内,加法后也可能发生溢出,加法后结果仍然可能溢出。


顺便说一句,我是一名刚毕业的研究生,正在寻找工作;)

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top