在C中签名为无符号转换 – 它总是安全吗?

假设我有下面的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. 如果两个操作数具有相同的types,则不需要进一步的转换。
  2. 否则,如果两个操作数都具有带符号的整数types或两者都具有无符号的整数types,则将具有较小整数转换等级的操作数转换为具有较高等级的操作数的types。
  3. 否则,如果具有无符号整数types的操作数的级别大于或等于另一操作数的types的级别,则将具有有符号整数types的操作数转换为具有无符号整数types的操作数的types。
  4. 否则,如果具有有符号整数types的操作数的types可以表示具有无符号整数types的操作数types的所有值,则将具有无符号整数types的操作数转换为具有有符号整数types的操作数的types。
  5. 否则,两个操作数都转换为与带符号整数types的操作数types相对应的无符号整数types。

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

6.3.1.3有符号和无符号整数

  1. 当整数types的值被转换为除_Bool以外的另一个整数types时,如果该值可以由新types表示,则该值不变。
  2. 否则,如果新types是无符号的,则通过重复地增加或减去新types中能够表示的最大值之一来转换该值,直到该值在新types的范围内。
  3. 否则,新的types被签名并且值不能被表示; 结果是实现定义的或实现定义的信号被引发。

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

6.2.5(9)

涉及无符号操作数的计算永远不会溢出,因为无法用结果的无符号整数types表示的结果被减less的模数大于可以由结果types表示的最大值的数。

奖金:算术转换Semi-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; } 

您可以使用此链接在线尝试: http : //codepad.org/yPhYCMFO

奖金:算术转换副作用

算术转换规则可以通过初始化一个无符号的值为-1来得到UINT_MAX的值,即:

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

由于上述转换规则,无论系统的签名号码表示如何,这都保证是可移植的。 看到这个问题的更多信息: 是否安全使用-1将所有位设置为true?

从signed到unsigned的转换不一定只是复制或重新解释签名值的表示。 引用C标准(C99 6.3.1.3):

当整数types的值被转换为除_Bool以外的另一个整数types时,如果该值可以由新types表示,则该值不变。

否则,如果新types是无符号的,则通过重复地增加或减去新types中能够表示的最大值之一来转换该值,直到该值在新types的范围内。

否则,新的types被签名并且值不能被表示; 结果是实现定义的或实现定义的信号被引发。

对于近来普遍使用的补码表示法,这些规则确实对应于重新解释这些比特。 但是对于其他表示(符号和数字或补码),C实现必须仍然安排相同的结果,这意味着转换不能只复制位。 例如,(无符号)-1 == UINT_MAX,而不pipe表示。

通常,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

(与unsigned overflow不同的是,signed overflow会调用未定义的行为,回旋是很常见的,但是C标准并不能保证这一点 – 编译器的优化会对代码造成严重破坏,从而导致不合理的假设)。

当一个无符号和一个有符号variables被添加(或任何二进制操作)都被隐式转换为无符号,在这种情况下会导致巨大的结果。

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

从signed到unsigned的转换有两种可能性。 最初是正数的数字仍然(或被解释为)相同的值。 现在被解释为更大的正数。

参考圣经 :

  • 您的加法操作导致int被转换为一个unsigned int。
  • 假设二进制补码表示和相同大小的types,位模式不会改变。
  • 从unsigned int到signed int的转换依赖于实现。 (但是现在大多数平台上的方法可能都是这样的。)
  • 在结合有符号和无符号的不同大小的情况下,规则稍微复杂一些。

正如以前所回答的那样,您可以在签名和未签名之间来回转换,而不会出现任何问题。 有符号整数的边界大小写是-1(0xFFFFFFFF)。 尝试添加和减去,你会发现你可以退回,并确保它是正确的。

然而,如果你要来回投射,我强烈build议命名你的variables,以便清楚它们是什么types,例如:

 int iValue, iResult; unsigned int uValue, uResult; 

被更重要的问题分散很容易,忘记哪个variables是什么types,如果他们被命名没有提示。 你不想投到一个无符号的,然后用它作为一个数组索引。

这里有什么隐含的转换,

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

这个代码对你和我的所有价值都是安全的吗?

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

这些规则通常是难以读懂的标准,但是在有符号的整数中使用的任何表示都是无符号整数,包含数字的二进制补码表示。

加法,减法和乘法将在这些数字上正确工作,从而得到另一个无符号整数,其中包含表示“真实结果”的二进制补码数字。

除法和转换为更大的无符号整数types将有明确的结果,但这些结果将不是“实际结果”的二进制补码表示。

(安全的,即使这个例子的结果会溢出到一些巨大的正数,我可以把它回到一个整数,并得到真正的结果。)

当从signed到unsigned的转换是由标准定义的,反过来是实现定义的,gcc和msvc都定义了转换,这样当将存储在无符号整数中的二进制数转换回有符号整数时,您将得到“实际结果” 。 我希望你只能在晦涩的系统上发现任何其他的行为,这些行为不使用2作为有符号整数的补码。

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

可怕的答案嘉豪

Ozgur Ozcitak

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

这是完全错误的。

垫子Fredriksson

当一个无符号和一个有符号variables被添加(或任何二进制操作)都被隐式转换为无符号,在这种情况下会导致巨大的结果。

这也是错误的。 由于无符号types的填充位,无符号整数可以被提升为整数。

SMH

您的加法操作导致int被转换为一个unsigned int。

错误。 也许这样做,也许没有。

从unsigned int到signed int的转换依赖于实现。 (但是现在大多数平台上的方法可能都是这样的。)

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

匿名

我的值被转换为无符号整数…

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

泰勒价格

正如以前所回答的那样,您可以在签名和未签名之间来回转换,而不会出现任何问题。

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

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

如果int的精度等于unsigned int,则u将被提升为一个带符号的int,并且将从expression式(u + i)中获得值-4444。 现在,如果你和我有其他的价值,你可能会得到溢出和未定义的行为,但确切的数字,你会得到-4444 [1] 。 该值将具有typesint。 但是你试图把这个值存储到一个unsigned int中,这样就会被转换为一个无符号的int值,而结果的值将会是(UINT_MAX + 1)-4444。

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

什么是精确度?

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

[陷阱]

如果存在填充位,macros的macros大小本身不能用于确定整数的精度。 并且字节的大小不一定是由C99定义的八位字节(八位)。

[1]溢出可能发生在两点之一。 在添加之前(在升级过程中) – 当你有一个unsigned int,这个int太大而不能放入int中。 即使unsigned int在int的范围之内,也可能在加法之后发生溢出,在加法之后结果可能仍然溢出。


在一个不相干的笔记上,我是一个最近研究生试图找工作;)