149

假设我有以下 C 代码。

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

unsigned int result = u + i;

u这里发生了哪些隐式转换,这段代码对于and的所有值是否安全i?(安全,即使这个例子中的结果会溢出到某个巨大的正数,我也可以将它转换回一个int并得到真正的结果。)

4

8 回答 8

247

简答

i将通过添加转换为无符号整数UINT_MAX + 1,然后将使用无符号值进行加法,从而产生较大result的值(取决于 和 的值ui

长答案

根据 C99 标准:

6.3.1.8 常用算术转换

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

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

6.3.1.3 有符号和无符号整数

  1. 当整数类型的值转换为_Bool以外的其他整数类型时,如果该值可以用新类型表示,则保持不变。
  2. 否则,如果新类型是无符号的,则通过在新类型中可以表示的最大值的基础上反复加减一,直到该值在新类型的范围内。
  3. 否则,新类型是有符号的,值不能在其中表示;结果是实现定义的,或者引发了实现定义的信号。

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

6.2.5 (9)

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

奖励:算术转换半 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,即:

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

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

于 2008-09-08T20:44:26.790 回答
24

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

当整数类型的值转换为_Bool以外的其他整数类型时,如果该值可以用新类型表示,则保持不变。

否则,如果新类型是无符号的,则通过在新类型中可以表示的最大值的基础上反复加减一,直到该值在新类型的范围内。

否则,新类型是有符号的,值不能在其中表示;结果是实现定义的,或者引发了实现定义的信号。

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

通常,C 中的转换被定义为对值进行操作,而不是对表示进行操作。

要回答原始问题:

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

unsigned int result = u + i;

i 的值被转换为无符号整数,产生UINT_MAX + 1 - 5678. 然后将该值添加到无符号值 1234,产生UINT_MAX + 1 - 4444.

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

于 2009-05-07T03:29:24.740 回答
6

参考The C Programming Language, Second Edition (ISBN 0131103628),

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

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

因此,从某种意义上说,结果可能是巨大的和错误的,但它永远不会崩溃,这是安全的。

于 2008-09-08T20:51:00.893 回答
3

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

于 2008-09-08T20:52:33.260 回答
1

如前所述,您可以毫无问题地在有符号和无符号之间来回转换。有符号整数的边界大小写为 -1 (0xFFFFFFFF)。尝试从中添加和减去,您会发现您可以回退并使其正确。

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

int iValue, iResult;
unsigned int uValue, uResult;

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

于 2008-09-08T21:08:12.193 回答
0

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

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

于 2016-02-07T12:08:14.000 回答
-19

可怕的答案嘉豪

奥兹古尔·奥兹齐塔克

当您从有符号转换为无符号(反之亦然)时,数字的内部表示不会改变。改变的是编译器如何解释符号位。

这是完全错误的。

马茨·弗雷德里克森

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

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

smh

您的加法操作会导致 int 转换为 unsigned int。

错误的。也许是这样,也许不是。

从 unsigned int 到 signed int 的转换取决于实现。(但它可能会按照你现在在大多数平台上所期望的方式工作。)

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

匿名的

i 的值被转换为 unsigned int ...

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

泰勒价格

如前所述,您可以毫无问题地在有符号和无符号之间来回转换。

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

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

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

如果 unsigned int 的精度大于 int 的精度,则有符号的 int 将被提升为 unsigned int 产生的值 (UINT_MAX+1) - 5678 将添加到另一个 unsigned int 1234。如果你和我有使表达式超出范围 {0..UINT_MAX} 的其他值,将添加或减去值 (UINT_MAX+1),直到结果确实落在范围 {0..UINT_MAX) 内并且不会发生未定义的行为.

什么是精度?

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

[陷阱]

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

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

于 2010-07-08T11:03:32.587 回答