在C中签名进行无符号转换-始终安全吗?
假设我有以下C代码。
unsigned int u = 1234; int i = -5678; unsigned int result = u + i;
这里正在进行什么隐式转换,并且此代码对于所有u和i值是否安全? (从某种意义上讲,即使这个示例中的结果将溢出到一个巨大的正数,我也可以将其强制转换为int并获得真实的结果。
解决方案
回答
简短答案
通过将UINT_MAX + 1加到i中,将其转换为无符号整数,然后将使用无符号值进行加法,从而导致较大的结果(取决于u和i的值) `)。
长答案
根据C99标准:
6.3.1.8 Usual arithmetic conversions If both operands have the same type, then no further conversion is needed. Otherwise, if both operands have signed integer types or both have unsigned integer types, the operand with the type of lesser integer conversion rank is converted to the type of the operand with greater rank. Otherwise, if the operand that has unsigned integer type has rank greater or equal to the rank of the type of the other operand, then the operand with signed integer type is converted to the type of the operand with unsigned integer type. Otherwise, if the type of the operand with signed integer type can represent all of the values of the type of the operand with unsigned integer type, then the operand with unsigned integer type is converted to the type of the operand with signed integer type. Otherwise, both operands are converted to the unsigned integer type corresponding to the type of the operand with signed integer type.
在情况下,我们有一个未签名的int(u
)和一个签名的int(i
)。参考上面的(3),由于两个操作数具有相同的等级,因此" i"将需要转换为无符号整数。
6.3.1.3 Signed and unsigned integers When a value with integer type is converted to another integer type other than _Bool, if the value can be represented by the new type, it is unchanged. Otherwise, if the new type is unsigned, the value is converted by repeatedly adding or subtracting one more than the maximum value that can be represented in the new type until the value is in the range of the new type. Otherwise, the new type is signed and the value cannot be represented in it; either the result is implementation-defined or an implementation-defined signal is raised.
现在我们需要参考上面的(2)。i
将通过添加UINT_MAX + 1
转换为无符号值。因此,结果将取决于在实现中如何定义" UINT_MAX"。它会很大,但不会溢出,因为:
6.2.5 (9) A computation involving unsigned operands can never overflow, because a result that cannot be represented by the resulting unsigned integer type is reduced modulo the number that is one greater than the largest value that can be represented by the resulting type.
奖励:算术转换半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
由于上述转换规则,因此保证了无论系统的带符号号码表示方式都可移植。有关更多信息,请参见此SO问题:使用-1将所有位设置为true是否安全?
回答
当添加一个无符号变量和一个带符号变量(或者任何二进制操作)时,两者都隐式转换为无符号,这将导致巨大的结果。
因此,从结果上可能是巨大的和错误的意义上讲,这是安全的,但它绝不会崩溃。
回答
从有符号转换为无符号时,有两种可能性。最初为正的数字保持(或者解释为)相同的值。原来是负数的数字现在将被解释为更大的正数。
回答
如先前的回答,我们可以在有符号和无符号之间来回转换,而不会出现问题。有符号整数的边界大小写为-1(0xFFFFFFFF)。尝试对其进行加法和减法,我们会发现我们可以回退并使其正确。
但是,如果要来回转换,我强烈建议我们命名变量,以便清楚地知道它们是什么类型,例如:
int iValue, iResult; unsigned int uValue, uResult;
太容易被更重要的问题分散注意力,而忘记了如果没有提示就将变量命名为哪种类型,则太容易了。我们不想强制转换为无符号,然后将其用作数组索引。
回答
参考圣经:
- 加法运算将int转换为unsigned int。
- 假设二进制补码表示且大小均等,则位模式不变。
- 从unsigned int到signed int的转换取决于实现。 (但是,这些天可能在大多数平台上都可以达到我们期望的方式。)
- 在组合大小不同的有符号和无符号的情况下,规则稍微复杂一些。
回答
从有符号到无符号的转换不一定只是复制或者重新解释有符号值的表示。引用C标准(C99 6.3.1.3):
When a value with integer type is converted to another integer type other than _Bool, if the value can be represented by the new type, it is unchanged. Otherwise, if the new type is unsigned, the value is converted by repeatedly adding or subtracting one more than the maximum value that can be represented in the new type until the value is in the range of the new type. Otherwise, the new type is signed and the value cannot be represented in it; either the result is implementation-defined or an implementation-defined signal is raised.
对于如今近乎普遍的二进制补码表示,规则确实对应于重新解释位。但是对于其他表示形式(符号和大小或者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标准不能保证-并且编译器优化会严重破坏无用假设的代码。)
回答
可怕的答案
奥兹古尔(Ozgur Ozcitak)
When you cast from signed to unsigned (and vice versa) the internal representation of the number does not change. What changes is how the compiler interprets the sign bit.
这是完全错误的。
马特斯·弗雷德里克森(Mats Fredriksson)
When one unsigned and one signed variable are added (or any binary operation) both are implicitly converted to unsigned, which would in this case result in a huge result.
这也是错误的。由于无符号类型中的填充位,如果无符号整数具有相同的精度,则可以将它们提升为整数。
h
Your addition operation causes the int to be converted to an unsigned int.
错误的。也许会,也许不会。
Conversion from unsigned int to signed int is implementation dependent. (But it probably works the way you expect on most platforms these days.)
错误的。如果它导致溢出或者保留该值,则它是未定义的行为。
匿名的
The value of i is converted to unsigned int ...
错误的。取决于int相对于unsigned int的精度。
泰勒·普莱斯
As was previously answered, you can cast back and forth between signed and unsigned without a problem.
错误的。试图存储一个有符号整数范围之外的值会导致未定义的行为。
现在,我终于可以回答这个问题了。
如果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将被添加到另一个unsigned int 1234. 值,使表达式落在{0..UINT_MAX}范围之外,将对值(UINT_MAX + 1)进行加或者减,直到结果DOES落在{0..UINT_MAX)范围内,并且不会发生未定义的行为。
什么是精度?
整数具有填充位,符号位和值位。无符号整数显然没有符号位。进一步保证了无符号字符没有填充位。整数具有的值位数是其精度。
[Gotchas]
如果存在填充位,则不能单独使用macrosizeof宏来确定整数的精度。并且字节的大小不必是C99定义的八位位组(八位)。
[1]溢出可能发生在两个点之一。如果unsigned int太大而无法容纳在int中,则在加法之前(升级期间)。即使无符号int在int范围内,加法后也可能发生溢出,加法后结果仍可能溢出。
无关紧要的是,我是一名正在寻找工作的研究生;)