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

前端 未结 8 1952
北荒
北荒 2020-11-22 01:55

Suppose I have the following C code.

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

unsigned int result = u + i;

What implicit conversions are goin

相关标签:
8条回答
  • 2020-11-22 02:36

    Short Answer

    Your i will be converted to an unsigned integer by adding UINT_MAX + 1, then the addition will be carried out with the unsigned values, resulting in a large result (depending on the values of u and i).

    Long Answer

    According to the C99 Standard:

    6.3.1.8 Usual arithmetic conversions

    1. If both operands have the same type, then no further conversion is needed.
    2. 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.
    3. 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.
    4. 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.
    5. Otherwise, both operands are converted to the unsigned integer type corresponding to the type of the operand with signed integer type.

    In your case, we have one unsigned int (u) and signed int (i). Referring to (3) above, since both operands have the same rank, your i will need to be converted to an unsigned integer.

    6.3.1.3 Signed and unsigned integers

    1. 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.
    2. 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.
    3. 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.

    Now we need to refer to (2) above. Your i will be converted to an unsigned value by adding UINT_MAX + 1. So the result will depend on how UINT_MAX is defined on your implementation. It will be large, but it will not overflow, because:

    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.

    Bonus: Arithmetic Conversion 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;
    }
    

    You can use this link to try this online: https://repl.it/repls/QuickWhimsicalBytes

    Bonus: Arithmetic Conversion Side Effect

    Arithmetic conversion rules can be used to get the value of UINT_MAX by initializing an unsigned value to -1, ie:

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

    This is guaranteed to be portable regardless of the signed number representation of the system because of the conversion rules described above. See this SO question for more information: Is it safe to use -1 to set all bits to true?

    0 讨论(0)
  • 2020-11-22 02:40

    As was previously answered, you can cast back and forth between signed and unsigned without a problem. The border case for signed integers is -1 (0xFFFFFFFF). Try adding and subtracting from that and you'll find that you can cast back and have it be correct.

    However, if you are going to be casting back and forth, I would strongly advise naming your variables such that it is clear what type they are, eg:

    int iValue, iResult;
    unsigned int uValue, uResult;
    

    It is far too easy to get distracted by more important issues and forget which variable is what type if they are named without a hint. You don't want to cast to an unsigned and then use that as an array index.

    0 讨论(0)
  • 2020-11-22 02:42

    When converting from signed to unsigned there are two possibilities. Numbers that were originally positive remain (or are interpreted as) the same value. Number that were originally negative will now be interpreted as larger positive numbers.

    0 讨论(0)
  • 2020-11-22 02:46

    Conversion from signed to unsigned does not necessarily just copy or reinterpret the representation of the signed value. Quoting the C standard (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.

    For the two's complement representation that's nearly universal these days, the rules do correspond to reinterpreting the bits. But for other representations (sign-and-magnitude or ones' complement), the C implementation must still arrange for the same result, which means that the conversion can't just copy the bits. For example, (unsigned)-1 == UINT_MAX, regardless of the representation.

    In general, conversions in C are defined to operate on values, not on representations.

    To answer the original question:

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

    The value of i is converted to unsigned int, yielding UINT_MAX + 1 - 5678. This value is then added to the unsigned value 1234, yielding UINT_MAX + 1 - 4444.

    (Unlike unsigned overflow, signed overflow invokes undefined behavior. Wraparound is common, but is not guaranteed by the C standard -- and compiler optimizations can wreak havoc on code that makes unwarranted assumptions.)

    0 讨论(0)
  • 2020-11-22 02:47

    Referring to the bible:

    • Your addition operation causes the int to be converted to an unsigned int.
    • Assuming two's complement representation and equally sized types, the bit pattern does not change.
    • Conversion from unsigned int to signed int is implementation dependent. (But it probably works the way you expect on most platforms these days.)
    • The rules are a little more complicated in the case of combining signed and unsigned of differing sizes.
    0 讨论(0)
  • 2020-11-22 02:50

    What implicit conversions are going on here,

    i will be converted to an unsigned integer.

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

    Safe in the sense of being well-defined yes (see https://stackoverflow.com/a/50632/5083516 ).

    The rules are written in typically hard to read standards-speak but essentially whatever representation was used in the signed integer the unsigned integer will contain a 2's complement representation of the number.

    Addition, subtraction and multiplication will work correctly on these numbers resulting in another unsigned integer containing a twos complement number representing the "real result".

    division and casting to larger unsigned integer types will have well-defined results but those results will not be 2's complement representations of the "real result".

    (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.)

    While conversions from signed to unsigned are defined by the standard the reverse is implementation-defined both gcc and msvc define the conversion such that you will get the "real result" when converting a 2's complement number stored in an unsigned integer back to a signed integer. I expect you will only find any other behaviour on obscure systems that don't use 2's complement for signed integers.

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

    0 讨论(0)
提交回复
热议问题