Go to main content
Oracle® Developer Studio 12.5: C User's Guide

Exit Print View

Updated: June 2017

7.3 Promotions: Unsigned Versus Value Preserving

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 change is considered to be the most serious made by the Committee to a widespread current practice.

This section explores how this change affects our code.

7.3.1 Some Background History

In the first edition of The C Programming Language, unsigned specified exactly one type, with no unsigned chars, unsigned shorts, or unsigned longs. Most C compilers added these 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 fewer unexpected arithmetic results for most expressions.

7.3.2 Compilation Behavior

Only in the transition or ISO modes (-Xt or -Xs) does the ISO C compiler use the unsigned preserving promotions. When -std=anyvalue is specified or in the other two modes, conforming (–Xc) and ISO (–Xa), the value preserving promotion rules are used.

7.3.3 Example: The Use of a Cast

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 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:

    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

Because 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.

7.3.4 Example: Same Result, No Warning

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.

7.3.5 Integral Constants

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

When you use the -xtransition option,the ISO C compiler warns you 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.

Note -  The rules for typing unsuffixed decimal constants has changed in accordance with the 1999 ISO C standard. See Integer Constants.

7.3.6 Example: 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 or when -std flag is specified.

Again, an appropriate cast clarifies the code and suppresses a warning:

-Xt, -Xs modes:
    i > (int)0xffff

-Xa, -Xc modes, or when -std flag is specified:
    i > (unsigned int)0xffff
    i > 0xffffU

The U suffix character is a new feature of ISO C and probably produces an error message with older compilers.