用 C 语言签名到无符号转换-它总是安全的吗?

假设我有以下 C 代码。

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


unsigned int result = u + i;

这里进行的是什么样的隐式转换,这段代码对于 ui的所有值是否安全?(安全,从这个意义上说,即使在这个例子中 结果将溢出到某个巨大的正数,我也可以将其转换回 Int并得到真正的结果。)

206058 次浏览

简短的回答

通过添加 UINT_MAX + 1,您的 i将为 转变了到一个无符号整数,然后将使用无符号值进行加法,从而得到一个较大的 result(取决于 ui的值)。

长答案

根据 C99标准:

6.3.1.8常用的算术转换

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

在您的例子中,我们有一个无符号整型(u)和有符号整型(i)。引用上面的(3) ,因为两个操作数有相同的排名,您的 i将需要是 转变了到一个无符号整数。

6.3.1.3有符号和无符号整数

  1. 当整数类型的值转换为 _ Bool 以外的其他整数类型时,如果该值可以由新类型表示,则该值不变。
  2. 否则,如果新类型是无符号的,则通过重复加减一个新类型中可以表示的最大值,直到该值在新类型的范围内为止,来转换该值。
  3. 否则,对新类型进行签名,并且该值不能在其中表示; 要么结果是实现定义的,要么产生实现定义的信号。

现在我们需要参考上面的(2)。通过添加 UINT_MAX + 1,您的 i将转换为无符号值。因此结果将取决于如何在实现上定义 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

额外奖励: 算术转换的副作用

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

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

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

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

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

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

如前所述,可以在有符号和无符号之间来回强制转换,没有问题。有符号整数的边框是 -1(0xFFFFFFFF)。尝试从中加减,你会发现你可以回溯,并有它是正确的。

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

int iValue, iResult;
unsigned int uValue, uResult;

太容易被更重要的问题分散注意力,如果没有提示就命名变量,就会忘记它们是什么类型。您不希望强制转换为无符号的,然后将其用作数组索引。

关于 C 编程语言,第二版(ISBN 0131103628) ,

  • 您的加法操作将导致整型转换为无符号整型。
  • 假设二的补数表示和大小相等的类型,位模式不会改变。
  • 从无符号整型到有符号整型的转换依赖于实现。(不过,如今在大多数平台上,它的工作方式可能和你预期的一样。)
  • 在组合不同大小的有符号和无符号的情况下,规则稍微复杂一些。

从有符号到无符号的转换,没有必然只是复制或重新解释有符号值的表示形式。引用 C 标准(C996.3.1.3) :

当整数类型的值转换为 _ Bool 以外的另一个整数类型时,如果 该值可以由新类型表示,但不会改变。

否则,如果新类型是无符号的,则通过重复添加或 减去多于新类型中可表示的最大值一个 直到该值在新类型的范围内。

否则,将对新类型进行签名,并且该值不能在其中表示; 结果是实现定义的或提出了实现定义的信号。

对于现在几乎普遍使用的二进制补语表示法,规则确实对应于重新解释比特。但是对于其他表示(符号和大小或者补语) ,C 实现仍然必须安排相同的结果,这意味着转换不能只是复制位。例如,(unsigned)-1 = = UINT _ MAX,不管表示形式如何。

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

回答最初的问题:

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


unsigned int result = u + i;

I 的值转换为无符号 int,生成 UINT_MAX + 1 - 5678。然后将该值添加到无符号值1234,生成 UINT_MAX + 1 - 4444

(与未签名溢出不同,签名溢出调用未定义行为。环绕(Wraparound)很常见,但 C 标准并不保证这一点——编译器优化可能会对做出不必要假设的代码造成严重破坏。)

可怕的答案

Ozgur Ozcitak

当您从已签名强制转换为未签名时 (反之亦然) 数字的表示形式则不会 改变的是 编译器解释符号位。

这是完全错误的。

Mats Fredriksson

一个没签,一个签 变量(或任何二进制文件) 操作)都是隐式的 转换为未签名,这将在 这个案子导致了巨大的结果。

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

您的加法操作将导致 int 转换为无符号整型。

错,也许有,也许没有。

从无符号整型到有符号整型的转换 Int 是依赖于实现的 可能跟你想的一样 现在大多数平台上都是如此。)

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

匿名的

I 的值转换为 无符号整型。

错误。取决于 int 相对于无符号 int 的精度。

泰勒 · 普莱斯

如前所述,你可以 在签名和 没有签名,没有问题。

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

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

如果整数的精度等于无符号整数,则 u 将被提升为有符号整数,并从表达式(u + i)中得到值 -4444。现在,如果 u 和 i 有其他值,你可能会得到溢出和未定义行为,但是有了这些精确的数字,你会得到 -4444 [1]。此值的类型为 int。但是您正在尝试将该值存储到一个无符号整数中,这样它就会被强制转换为一个无符号整数,并且最终得到的值将是(UINT _ MAX + 1)-4444。

如果无符号整数的精度大于整数,则有符号整数将被提升为无符号整数,产生值(UINT _ MAX + 1)-5678,该值将被添加到另一个无符号整数1234中。如果 u 和 i 有其他值,使表达式落在{0.UINT _ MAX }的范围之外,那么该值(UINT _ MAX + 1)将被加或减,直到结果 DOES 落在{0.UINT _ MAX 的范围之内,并且不会发生未定义行为。

什么是精确度?

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

[ Gotchas ]

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

溢出可能发生在两个点之一。无论是在加法之前(在提升过程中)-当你有一个无符号整数,它太大以至于无法放入一个整数。即使无符号整数在整数的范围内,在加法之后也可能发生溢出,在加法之后,结果仍然可能溢出。

这里发生的隐式转换,

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

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

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

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

加法、减法和乘法可以正确地处理这些数字,从而产生另一个无符号整数,其中包含一个代表“实际结果”的二进制补数。

除法和强制转换为更大的无符号整数类型将具有定义良好的结果,但是这些结果将不是“实际结果”的2的补数表示。

(安全,在这个意义上,即使在这个例子中的 result 将溢出为某个巨大的正数,我也可以将其转换回 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