在C中签名进行无符号转换-始终安全吗?


135

假设我有以下C代码。

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

unsigned int result = u + i;

这里正在进行什么隐式转换,并且此代码对uand的所有值安全i吗?(安全,就算这个示例中的结果将溢出到一个巨大的正数,我也可以将其强制转换为int并获得真实的结果。)

Answers:


223

简短答案

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是否安全?


我不明白为什么它不能简单地做一个绝对值,然后像正数一样对待它是无符号的?
Jose Salvatierra

7
@ D.Singh您能否指出答案中的错误部分?
Shmil The Cat 2014年

为了将有符号转换为无符号,我们将无符号值的最大值(UINT_MAX +1)相加。同样,从无符号转换为有符号的简单方法是什么?我们是否需要从最大值中减去给定的数字(如果是无符号字符,则为256)?例如:140转换为带符号数字时将变为-116。但是20变成20本身。那么这里有什么简单的把戏吗?
乔恩·会德丰


24

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

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

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

否则,将对新类型进行签名,并且无法在其中表示值;结果是实现定义的,还是引发实现定义的信号。

对于如今近乎普遍的二进制补码表示,规则确实对应于重新解释位。但是对于其他表示形式(符号和大小或1的补码),C实现必须仍然安排相同的结果,这意味着转换不能只是复制这些位。例如,(unsigned)-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标准不能保证这种环绕-编译器的优化可能会对进行不必要假设的代码造成严重破坏。)


5

圣经

  • 您的加法运算将int转换为unsigned int。
  • 假设二进制补码表示并且大小均等,则位模式不变。
  • 从unsigned int到signed int的转换取决于实现。(但是,这几天可能在大多数平台上都可以达到预期的效果。)
  • 在组合大小不同的有符号和无符号的情况下,规则稍微复杂一些。

3

当添加一个无符号变量和一个带符号变量(或任何二进制操作)时,二者都隐式转换为无符号,这将导致巨大的结果。

因此,从结果上可能是巨大的和错误的意义上讲,这是安全的,但绝不会崩溃。


不对。6.3.1.8常规算术转换如果将一个int和一个无符号char相加,则后者将转换为int。如果将两个无符号字符求和,它们将转换为int。
2501年

3

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


1

如先前的回答,您可以在有符号和无符号之间来回转换,而不会出现问题。有符号整数的边界大小写为-1(0xFFFFFFFF)。尝试对其进行加法和减法,您会发现可以回退并使其正确。

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

int iValue, iResult;
unsigned int uValue, uResult;

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


0

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

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

这段代码对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/zh-CN/library/0eex498h.aspx


-17

可怕的答案

Ozgur Ozcitak

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

这是完全错误的。

马特斯·弗雷德里克森

当添加一个无符号变量和一个带符号变量(或任何二进制操作)时,二者都隐式转换为无符号,这将导致巨大的结果。

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

h

您的加法运算将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。但是您正在尝试将该值存储到一个无符号的int中,以便随后将其转换为一个无符号的int,结果最终将具有(UINT_MAX + 1)-4444。

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

什么是精度?

整数具有填充位,符号位和值位。无符号整数显然没有符号位。进一步保证了无符号字符没有填充位。整数具有的值位数是其精度。

[Gotchas]

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

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


6
“未签名的整数可以提升为整数”。不对。由于类型已经是>> int类型,因此不会发生整数提升。6.3.1.1:“任何无符号整数类型的等级应等于相应的有符号整数类型的等级(如果有)。” 和6.3.1.8:“否则,如果具有无符号整数类型的操作数的秩大于或等于另一个操作数的类型的秩,则将带符号整数类型的操作数转换为具有无符号整数的操作数的类型类型。” 两者都保证在应用常规算术转换时int会转换为unsigned int
CB Bailey 2010年

1
6.3.1.8仅在整数提升后发生。开篇段落说:“否则,对两个操作数执行整数提升。然后将以下规则应用于提升的操作数”。因此,请阅读提升规则6.3.1.1 ...“具有整数类型的对象或表达式,其整数转换等级小于int和unsigned int的等级或等于EQUAL”和“如果int可以表示整数的所有值原始类型,则将值转换为“ int”。
Elite Mx

1
6.3.1.1整数促销二手转换不属于某个整数类型intunsigned int于那些类型的东西类型中的一种unsigned intint预期。在TC2中添加了“或等于”,以允许枚举类型的转换等级等于intunsigned int转换为这些类型之一。从来没有想过所描述的促销会在unsigned int和之间转换intunsigned int和之间的通用类型确定int仍受6.3.1.8(即使在TC2之后)支配。
CB Bailey 2010年

19
发布错误答案同时批评别人的错误答案听起来并不像是上班的好策略... ;-)
R .. GitHub停止帮助ICE 2010年

6
我不赞成删除,因为这种错误和自大的结合太过有趣了
MM
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.