union Positive_Small {
int8_t s;
uint8_t u;
};
union Positive_Small x = {.s = 3};
union Positive_Small y = {.u = 4};
assert(x.u == 3);
assert(y.s == 4);
Is this defined behaviour? Does the Standard guarantee that the positive range of a signed integral type has the same representation as its unsigned equivalent?
I imagine that there is no implementation crazy enough (DS9K maybe?) to not do it, but is it defined?
Succinctly, yes — the standard guarantees that for the shared positive range of values, the bitwise representation of the values for the unsigned type are the same as for the signed type.
C11 Section 6.2.5 Types defines this (and a lot of other terminology and behaviour):
¶6 For each of the signed integer types, there is a corresponding (but different) unsigned integer type (designated with the keyword
unsigned
) that uses the same amount of storage (including sign information) and has the same alignment requirements. The type_Bool
and the unsigned integer types that correspond to the standard signed integer types are the standard unsigned integer types. The unsigned integer types that correspond to the extended signed integer types are the extended unsigned integer types. The standard and extended unsigned integer types are collectively called unsigned integer types.40)¶9 The range of nonnegative values of a signed integer type is a subrange of the corresponding unsigned integer type, and the representation of the same value in each type is the same.41) 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.
40) Therefore, any statement in this Standard about unsigned integer types also applies to the extended unsigned integer types.
41) The same representation and alignment requirements are meant to imply interchangeability as arguments to functions, return values from functions, and members of unions.
As dbush pointed out, section 6.2.6 Representation of types, and section 6.2.6.2 Integer types specifically, also contains relevant information:
¶2 For signed integer types, the bits of the object representation shall be divided into three groups: value bits, padding bits, and the sign bit. There need not be any padding bits;
signed char
shall not have any padding bits. There shall be exactly one sign bit. Each bit that is a value bit shall have the same value as the same bit in the object representation of the corresponding unsigned type (if there are M value bits in the signed type and N in the unsigned type, then M ≤ N). If the sign bit is zero, it shall not affect the resulting value. If the sign bit is one, the value shall be modified in one of the following ways:
- the corresponding value with sign bit 0 is negated (sign and magnitude);
- the sign bit has the value -(2M) (two's complement);
- the sign bit has the value -(2M- 1) (ones' complement).
Which of these applies is implementation-defined, as is whether the value with sign bit 1 and all value bits zero (for the first two), or with sign bit and all value bits 1 (for ones' complement), is a trap representation or a normal value. In the case of sign and magnitude and ones' complement, if this representation is a normal value it is called a negative zero.