C integer promotions

Intro

Summarized reference from the book “The Art of Software Security Assessment – Identifying and Preventing Software Vulnerabilities” By Mark Dowd, John McDonald, Justin Schuh.

Please go read this chapter and book in its entirety. You won’t be disappointed.

Integer Promotions

Integer promotions specify how C takes a narrow integer data type, such as a char or short, and converts it to an int (or, in rare cases, to an unsigned int). This up-conversion, or promotion, is used for two different purposes:

  • Certain operators in C require an integer operand of type int or unsigned int. For these operators, C uses the integer promotion rules to transform a narrower integer operand into the correct typeint or unsigned int.
  • Integer promotions are a critical component of C’s rules for handling arithmetic expressions, which are called the usual arithmetic conversions.
    For arithmetic expressions involving integers, integer promotions are usually applied to both operands.

There’s a useful concept from the C standards: Each integer data type is assigned what’s known as an integer conversion rank. These ranks order the integer data types by their width from lowest to highest. The signed and unsigned varieties of each type are assigned the same rank. The following abridged list sorts integer types by conversion rank from high to low. The C standard assigns ranks to other integer types, but this list should suffice for this discussion:

Integer conversion rank

long long int, unsigned long long int
long int, unsigned long int
unsigned int, int
unsigned short, short
char, unsigned char, signed char
_Bool

Integer Promotions Summary
If you apply the integer promotions to a variable, what happens? First, if the variable isn’t an integer type or a bit field, the promotions do nothing. Second, if the variable is an integer type, but its integer conversion rank is greater than or equal to that of an int, the promotions do nothing. Therefore, ints, unsigned ints, long ints, pointers, and floats don’t get altered by the integer promotions.
So, the integer promotions are responsible for taking a narrower integer type or bit field and promoting it to an int or unsigned int. This is done in a straightforward fashion: If a value-preserving transformation to an int can be performed, it’s done. Otherwise, a value-preserving conversion to an unsigned int is performed.

The basic rule of thumb is this: If an integer type is narrower than an int, integer promotions almost always convert it to an int. A few common types.

Results of Integer Promotions

Source Type Result Type Rationale
unsigned char int Promote; source rank less than int rank
char int Promote; source rank less than int rank
short int Promote; source rank less than int rank
unsigned short int Promote; source rank less than int rank
unsigned int: 24 int Promote; bit field of unsigned int
unsigned int: 32 unsigned int Promote; bit field of unsigned int
int int Don’t promote; source rank equal to int rank
unsigned int unsigned int Don’t promote; source rank equal to int rank
long int long int Don’t promote; source rank greater than int rank
float float Don’t promote; source not of integer type
char * char * Don’t promote; source not of integer type

Integer Promotion Applications

Unary + Operator

The unary + operator performs integer promotions on its operand. For example, if the bob variable is of type char, the resulting type of the expression (+bob) is int, whereas the resulting type of the expression (bob) is char.

Unary – Operator

The unary – operator does integer promotion on its operand and then does a negation. Regardless of whether the operand is signed after the promotion, a twos complement negation is performed, which involves inverting the bits and adding 1.

Unary ~ Operator

The unary ~ operator does a ones complement of its operand after doing an integer promotion of its operand. This effectively performs the same operation on both signed and unsigned operands for twos complement implementations: It inverts the bits.

Bitwise Shift Operators

The bitwise shift operators >> and << shift the bit patterns of variables. The integer promotions are applied to both arguments of these operators, and the type of the result is the same as the promoted type of the left operand, as shown in this example:

char a = 1;
char c = 16;
int bob;
bob = a << c;

a is converted to an integer, and c is converted to an integer. The promoted type of the left operand is int, so the type of the result is an int. The integer representation of a is left-shifted 16 times.

Switch Statements

Integer promotions are used in switch statements. The general form of a switch statement is something like this:

switch (controlling expression)
{ 
    case (constant integer expression): body;
        break; 
    default: body; 
        break; 
}

The integer promotions are used in the following way: First, they are applied to the controlling expression, so that expression has a promoted type. Then, all the integer constants are converted to the type of the promoted control expression.

Function Invocations

Older C programs using the K&R semantics don’t specify the data types of arguments in their function declarations. When a function is called without a prototype, the compiler has to do something called default argument promotions. Basically, integer promotions are applied to each function argument, and any arguments of the float type are converted to arguments of the double type. Consider the following example:

int jim(bob)
char bob;
{
    printf("bob=%d\n", bob);
}

int main(int argc, char **argv)
{
    char a=5;
    jim(a);
}

 

In this example, a copy of the value of a is passed to the jim() function. The char type is first run through the integer promotions and transformed into an integer. This integer is what’s passed to the jim() function. The code the compiler emits for the jim() function is expecting an integer argument, and it performs a direct conversion of that integer back into a char format for the bob variable.

Usual Arithmetic Conversions

In many situations, C is expected to take two operands of potentially divergent types and perform some arithmetic operation that involves both of them. The C standards spell out a general algorithm for reconciling two types into a compatible type for this purpose. This procedure is known as the usual arithmetic conversions. The goal of these conversions is to transform both operands into a common real type, which is used for the actual operation and then as the type of the result. These conversions apply only to the arithmetic types integer and floating point types. The following sections tackle the conversion rules.