假设我有以下C代码。
unsigned int u = 1234;
int i = -5678;
unsigned int result = u + i;
此处发生了哪些隐式转换,此代码对u
和i
的所有值都安全吗? (安全,从某种意义上说,即使这个例子中的结果会溢出到一些巨大的正数,我也可以将其转换回 int 并获得真实的结果。)
答案 0 :(得分:198)
简答
通过添加i
,您的UINT_MAX + 1
将转换为无符号整数,然后将使用无符号值执行添加,从而导致{{1} (取决于result
和u
)的值。
长答案
根据C99标准:
6.3.1.8通常的算术转换
- 如果两个操作数具有相同的类型,则不需要进一步转换。
- 否则,如果两个操作数都有有符号整数类型或两者都有无符号整数类型,则具有较小整数转换等级类型的操作数将转换为具有更高等级的操作数类型。
- 否则,如果具有无符号整数类型的操作数的秩大于或等于另一个操作数的类型的秩,则带有符号整数类型的操作数将转换为具有无符号整数类型的操作数的类型。 / LI>
- 否则,如果带有符号整数类型的操作数的类型可以表示具有无符号整数类型的操作数类型的所有值,则具有无符号整数类型的操作数将转换为带有符号整数的操作数的类型类型。
- 否则,两个操作数都将转换为无符号整数类型,对应于带有符号整数类型的操作数类型。
醇>
在您的情况下,我们有一个unsigned int(i
)和signed int(u
)。参考上面的(3),由于两个操作数具有相同的等级,因此您的i
需要转换为无符号整数。
6.3.1.3有符号和无符号整数
- 当整数类型的值转换为_Bool以外的另一个整数类型时,如果该值可以用新类型表示,则它将保持不变。
- 否则,如果新类型是无符号的,则通过重复地添加或减去一个可以在新类型中表示的最大值来转换该值,直到该值在新类型的范围内。
- 否则,新类型已签名且值无法在其中表示;结果是实现定义的,或者引发实现定义的信号。
醇>
现在我们需要参考上面的(2)。您的i
将通过添加i
转换为无符号值。因此,结果将取决于您的实现如何定义UINT_MAX + 1
。它会很大,但不会溢出,因为:
6.2.5(9)
涉及无符号操作数的计算永远不会溢出,因为无法用结果无符号整数类型表示的结果将以比结果类型可以表示的最大值大1的数量为模。
奖励:算术转换半WTF
UINT_MAX
您可以使用此链接在线试用:https://repl.it/repls/QuickWhimsicalBytes
奖励:算术转换副作用
算术转换规则可用于通过将无符号值初始化为#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;
}
来获取UINT_MAX
的值,即:
-1
由于上述转换规则,无论系统的带符号数表示如何,都保证可以移植。有关详细信息,请参阅此SO问题:Is it safe to use -1 to set all bits to true?
答案 1 :(得分:19)
从签名转换为无签名不必须只复制或重新解释签名值的表示。引用C标准(C99 6.3.1.3):
当整数类型的值转换为_Bool以外的另一个整数类型时,if 该值可以用新类型表示,不变。
否则,如果新类型是无符号的,则通过重复添加或转换该值 减去一个可以在新类型中表示的最大值 直到该值在新类型的范围内。
否则,新类型已签名且值无法在其中表示;无论是 结果是实现定义的或引发实现定义的信号。
对于现在几乎普遍的二进制补码表示,规则确实对应于重新解释位。但对于其他表示(符号和幅度或1'补码),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标准无法保证 - 并且编译器优化会对代码造成严重破坏。)
答案 2 :(得分:4)
参考the bible:
答案 3 :(得分:3)
当添加一个无符号和一个有符号变量(或任何二进制操作)时,两者都被隐式转换为无符号,这在这种情况下会产生巨大的结果。
因此,结果可能是巨大而错误的,这是安全的,但它永远不会崩溃。
答案 4 :(得分:3)
从有符号转换为无符号时,有两种可能性。最初为正的数字保持(或被解释为)相同的值。最初为负的数字现在将被解释为更大的正数。
答案 5 :(得分:1)
如前所述,您可以在签名和未签名之间来回转换而不会出现问题。有符号整数的边界情况是-1(0xFFFFFFFF)。尝试添加和减去它,你会发现你可以退回并让它正确。
但是,如果您要来回演绎,我强烈建议您命名变量,以便明确它们的类型,例如:
int iValue, iResult;
unsigned int uValue, uResult;
很容易被更重要的问题分散注意力,忘记哪个变量是没有提示而被命名的类型。您不希望强制转换为无符号,然后将其用作数组索引。
答案 6 :(得分:0)
这里发生了什么隐式转换,
我将被转换为无符号整数。
并且这段代码对于你和我的所有值都是安全的吗?
在明确定义的意义上是安全的(参见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
答案 7 :(得分:-16)
可怕的答案嘉豪
Ozgur Ozcitak
从签名转为无签名时 (反之亦然)内部 数字的表示没有 更改。怎么变化是怎么回事 编译器解释符号位。
这是完全错误的。
Mats Fredriksson
当一个未签名且一个签名时 变量被添加(或任何二进制 操作)两者都是隐含的 转换为无符号,将在 这种情况导致了巨大的结果。
这也是错误的。如果无符号整数由于无符号类型中的填充位而具有相同的精度,则可以将其提升为整数。
SMH
您的添加操作会导致int 转换为unsigned int。
错误。也许它确实如此,也许它没有。
从unsigned int转换为signed int是依赖于实现的。 (但 它可能按照你期望的方式工作 这些天在大多数平台上。)
错误。如果它导致溢出或保留值,则它是未定义的行为。
匿名
i的值转换为 unsigned int ...
错误。取决于相对于unsigned int的int的精度。
泰勒价格
如前所述,你可以 在签名和签名之间来回投掷 没有签名没有问题。
错误。尝试存储超出有符号整数范围的值会导致未定义的行为。
现在我终于可以回答这个问题了。
如果int的精度等于unsigned int,则u将被提升为signed int,并且您将从表达式(u + i)获得值-4444。现在,如果您和我有其他值,您可能会遇到溢出和未定义的行为,但是使用这些确切的数字,您将得到-4444 [1] 。该值将具有int类型。但是你试图将该值存储到unsigned int中,然后将其转换为unsigned int,结果将导致的值为(UINT_MAX + 1) - 4444。
如果unsigned int的精度大于int的精度,则signed int将被提升为unsigned int,产生值(UINT_MAX + 1) - 5678,它将被添加到另一个unsigned int 1234.如果你我有其他值,使表达式超出范围{0..UINT_MAX},值(UINT_MAX + 1)将被添加或减去,直到结果DOES落在范围{0..UINT_MAX)内并且没有未定义行为将会发生。
什么是精确度?
整数具有填充位,符号位和值位。无符号整数显然没有符号位。无符号字符进一步保证不具有填充位。整数的值位数是它具有多少精度。
[陷阱]
如果存在填充位,则单独的宏sizeof宏不能用于确定整数的精度。并且字节的大小不必是C99定义的八位字节(八位)。
[1] 溢出可能发生在两点之一。在添加之前(在提升期间) - 当你有一个无符号的int,它太大而不适合int。添加后也可能发生溢出,即使unsigned int在int范围内,添加后结果仍可能溢出。
在一个不相关的说明中,我是一名最近想找工作的研究生;)