1
votes

I can not find anywhere in the C standard that justifies the following:

int n = -0x80000000 // set n to -2^31

Assume an implementation where int is 32 bits. The apparent problem is that the integer constant has type unsigned int, as per the table in the committee draft standard at 6.4.4.1 paragraph 5. Then the negation is calculated according to 6.5.3.3 paragraph 3:

The result of the unary - operator is the negative of its (promoted) operand. The integer promotions are performed on the operand, and the result has the promoted type.

Performing the integer promotions does not change the type (unsigned int stays unsigned int). Then the negative is taken. Since the result retains the promoted type, it is reduced modulo 2^32, producing 2^31 (so the negation has no effect).

Assigning an out of range value to type int is covered by the following:

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

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.

So, in the end, we get implementation defined behavior when we try to assign a valid int value to an int object (assuming 2's complement with no trap representation).

The following would be standard guaranteed to have the expected result:

int n = -(long long)0x80000000 // set n to -2^31

So, do you really need to cast up to validly make an in range assignment, or am I missing something?

5
Note about "standard guaranteed to have the expected result" --> recall int can be 16-bit.chux - Reinstate Monica
Yes, I am assuming a 32 bit int.Kyle
The problem is actually simple, don't use hex to describe values that possibly can become negative. Hex values are a good tool when you want to describe bit patterns, to be used as masks in bit operations and alike. When you want to describe values for signed arithmetic the bit representation should be irrelevant.Jens Gustedt
Congratulations, you have found out that your code int n = -0x80000000 makes no sense. If you write really weird code, you will often trigger really weird C standard behavior as well.Lundin
@Lundin, the point was to confirm my understanding that '-' is unreliable applied to arbitrary integer type arguments, not to write that particular line. And macros using '-' do not constitute "really weird code".Kyle

5 Answers

2
votes

I'm afraid you remark Assigning an out of range value to type int is covered by the following: does not apply to

unsigned int n = -0x80000000 // set n to -2^31

n has type unsigned int and the value 2^31 is not out of range for 32 bits unsigned int.

EDIT: since you changed the question and made n an int, then 3 applies for 32 bit and lesser ints and the comment is incorrect for larger int types:

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.

EDIT: the second code fragment int n = -(long long)0x80000000 // set n to -2^31 is correct as the value -2147483648 does fit in a 32 bit int.

Note that the correct way to initialize n to this value is this (assuming 32 bit ints):

int n = -2147483647 - 1;  // set n to -2^31

This variant is used in the standard headers <limits.h> and <stdint.h>. It does not use casts, so the answer to you last question is: no, you do not really need to cast up to validly make an in range assignment for -2^31 to a 32 bit int.

1
votes

If INT_MAX is 0x7fffffff, then as a hex literal, 0x80000000 has type unsigned int or larger, and applying - to it is safe. This would not be true for decimal literals. If INT_MAX were larger than 0x7fffffff then the negation would already be safe as int.

With the edited question now assigning the result into an object of type int, the conversion from an out-of-range value to int is implementation-defined. In practice it's always defined as what you want (modular reduction), but this is not guaranteed by the standard. All the standard guarantees is that the implementation has to document how the conversion takes place.

1
votes

int n = -(long long)0x80000000

do you really need to cast up to validly make an in range assignment, or am I missing something?

How about: int n = -0x80000000LL;

No cast.

0
votes

Simply do int n = INT_MIN.

Or if you must int n = -2147483648 (same as casting to long long).

Mixing hex literals and negative numbers in C is usually a bad idea, since hex literals assume a specific signedness format.

we get implementation defined behavior when we try to assign a valid int value to an int object

If you are concerned about implementation-defined behavior, then why do you use the naive int type and not int32_t?

int has implementation-defined size and signedness format, which is the root of the problem. int32_t is guaranteed to be 32 bits two's complement.

0
votes

The relative behavior of signed and unsigned types in C is a mess that came about when the C89 Committee tried to formulate rules that would be as consistent as possible with the behavior of pre-existing compilers that were often inconsistent with each other.

There isn't really any portable way to ensure that expressions involving integer constants will work in expected fashion except to manually ensure that they're promoted to a type that's guaranteed big enough to hold all intermediate values; if you don't need anything beyond 64 bits, either "long long" or "unsigned long long" should suffice; values can be forced to those types using a UL or ULL suffix.