关于在C中签名为无符号转换:在C中签名为无符号-始终安全吗?

关于在C中签名为无符号转换:在C中签名为无符号-始终安全吗?

Signed to unsigned conversion in C - is it always safe?

假设我有以下C代码。

1
2
3
4
unsigned int u = 1234;
int i = -5678;

unsigned int result = u + i;

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


简短答案

您的i将通过加UINT_MAX + 1转换为无符号整数,然后将以无符号值进行加法,从而导致较大的result(取决于ui的值) 。

长答案

根据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)。通过添加UINT_MAX + 1,您的i将转换为无符号值。因此,结果将取决于在实现中如何定义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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    #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的值,即:

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

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


    从有符号到无符号的转换不一定只是复制或重新解释有符号值的表示。引用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中的转换定义为对值而不是表示形式进行操作。

    要回答原始问题:

    1
    2
    3
    4
    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标准不能保证这种环绕-编译器的优化可能会对进行不必要假设的代码造成严重破坏。)


    参考圣经:

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

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


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

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


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

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

    1
    2
    int iValue, iResult;
    unsigned int uValue, uResult;

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


    What implicit conversions are going on here,

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

    and is this code safe for all values of u and i?

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

    规则通常用难以理解的标准来编写,但是本质上无论有符号整数使用哪种表示形式,无符号整数都将包含数字的2的补码表示形式。

    加,减和乘将在这些数字上正常工作,从而导致另一个无符号整数包含表示"实际结果"的二进制补码。

    除法和转换为较大的无符号整数类型将具有定义明确的结果,但这些结果将不是"真实结果"的2的补码表示。

    (Safe, in the sense that even though result in this example will overflow to some huge positive number, I could cast it back to an int and get the real result.)

    虽然标准定义了从有符号到无符号的转换,但是反向实现是由实现定义的,gcc和msvc都定义了转换,这样当将无符号整数中存储的2的补码转换回有符号整数时,您将获得"真实结果" 。我希望您只会在不使用2的补码作为有符号整数的晦涩系统上找到其他行为。

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


    可怕的答案

    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.

    这是完全错误的。

    马特斯·弗雷德里克森

    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.

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

    SMH

    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,并将其添加到另一个无符号的int 1234中。其他值,这些表达式使表达式落在{0..UINT_MAX}范围之外,将添加或减去值(UINT_MAX + 1),直到结果DOES落在{0..UINT_MAX)范围内,并且不会发生未定义的行为。

    什么是精度?

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

    [陷阱]

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

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

    无关紧要的是,我是一名正在寻找工作的研究生;)


    推荐阅读