The following information appears in the Rationale section that accompanies the 1990 ISO C Standard: “QUIET CHANGE”. A program that depends on unsigned preserving arithmetic conversions will behave differently, probably without complaint. This is considered to be the most serious change made by the Committee to a widespread current practice.
This section explores how this change affects our code.
According to K&R, The C Programming Language (First Edition), unsigned specified exactly one type; there were no unsigned chars, unsigned shorts, or unsigned longs, but most C compilers added these very soon thereafter. Some compilers did not implement unsigned long but included the other two. Naturally, implementations chose different rules for type promotions when these new types mixed with others in expressions.
In most C compilers, the simpler rule, “unsigned preserving,” is used: when an unsigned type needs to be widened, it is widened to an unsigned type; when an unsigned type mixes with a signed type, the result is an unsigned type.
The other rule, specified by ISO C, is known as “value preserving,” in which the result type depends on the relative sizes of the operand types. When an unsigned char or unsigned short is widened, the result type is int if an int is large enough to represent all the values of the smaller type. Otherwise, the result type is unsigned int. The value preserving rule produces the least surprise arithmetic result for most expressions.
Only in the transition or ISO modes (-Xt or -Xs) does the ISO C compiler use the unsigned preserving promotions; in the other two modes, conforming (–Xc) and ISO (–Xa), the value preserving promotion rules are used.
In the following code, assume that an unsigned char is smaller than an int.
int f(void) { int i = -2; unsigned char uc = 1; return (i + uc) < 17; } |
The code above causes the compiler to issue the following warning when you use the -xtransition option:
line 6: warning: semantics of "<" change in ISO C; use explicit cast
The result of the addition has type int (value preserving) or unsigned int (unsigned preserving), but the bit pattern does not change between these two. On a two’s-complement machine, we have:
i: 111...110 (-2) + uc: 000...001 ( 1) =================== 111...111 (-1 or UINT_MAX) |
This bit representation corresponds to -1 for int and UINT_MAX for unsigned int. Thus, if the result has type int, a signed comparison is used and the less-than test is true; if the result has type unsigned int, an unsigned comparison is used and the less-than test is false.
The addition of a cast serves to specify which of the two behaviors is desired:
value preserving: (i + (int)uc) < 17 unsigned preserving: (i + (unsigned int)uc) < 17 |
Since differing compilers chose different meanings for the same code, this expression can be ambiguous. The addition of a cast is as much to help the reader as it is to eliminate the warning message.
The same situation applies to the promotion of bit-field values. In ISO C, if the number of bits in an int or unsigned int bit-field is less than the number of bits in an int, the promoted type is int; otherwise, the promoted type is unsigned int. In most older C compilers, the promoted type is unsigned int for explicitly unsigned bit-fields, and int otherwise.
Similar use of casts can eliminate situations that are ambiguous.
In the following code, assume that both unsigned short and unsigned char are narrower than int.
int f(void) { unsigned short us; unsigned char uc; return uc < us; } |
In this example, both automatics are either promoted to int or to unsigned int, so the comparison is sometimes unsigned and sometimes signed. However, the C compiler does not warn you because the result is the same for the two choices.
As with expressions, the rules for the types of certain integral constants have changed. In K&R C, an unsuffixed decimal constant had type int only if its value fit in an int; an unsuffixed octal or hexadecimal constant had type int only if its value fit in an unsigned int. Otherwise, an integral constant had type long. At times, the value did not fit in the resulting type. In the 1990 ISO/IEC C standard, the constant type is the first type encountered in the following list that corresponds to the value:
unsuffixed decimal: int, long, unsigned long
unsuffixed octal or hexadecimal: int, unsigned int, long, unsigned long
U suffixed: unsigned int, unsigned long
L suffixed: long, unsigned long
UL suffixed: unsigned long
The ISO C compiler warns you, when you use the -xtransition option, about any expression whose behavior might change according to the typing rules of the constants involved. The old integral constant typing rules are used only in the transition mode; the ISO and conforming modes use the new rules.
The rules for typing unsuffixed decimal constants has changed in accordance with the 1999 ISO C standard. See 2.1.1 Integral Constants .
In the following code, assume ints are 16 bits.
int f(void) { int i = 0; return i > 0xffff; } |
Because the hexadecimal constant’s type is either int (with a value of– 1 on a two’s-complement machine) or an unsigned int (with a value of 65535), the comparison is true in– Xs and -Xt modes, and false in– Xa and– Xc modes.
Again, an appropriate cast clarifies the code and suppresses a warning:
-Xt, -Xs modes: i > (int)0xffff -Xa, -Xc modes: i > (unsigned int)0xffff or i > 0xffffU |
The U suffix character is a new feature of ISO C and probably produces an error message with older compilers.