C Mastery / Conversions: Integer Promotions and Usual Arithmetic Conversions
Part 1 — The Core Language

Conversions: Integer Promotions and Usual Arithmetic Conversions

This chapter explains the two conversion mechanisms that govern almost every arithmetic operation in C: the integer promotions and the usual arithmetic conversions. These rules determine what type an expression has and are the source of many subtle signed/unsigned bugs.

Why This Matters

When you write a + b where a is int and b is unsigned int, the compiler converts one of them before adding. Getting this wrong — usually by not knowing that int converts to unsigned int — produces surprising comparison and arithmetic results that are often security bugs.

Prerequisites

Core Concept

Integer promotions

In many contexts, small integer types (char, short, and their signed/ unsigned variants, plus bit-fields and _Bool) are promoted to int (or unsigned int if int cannot represent all values of the source type) before the operation.

The rule: if an int can represent all values of the original type, the value is converted to int; otherwise, to unsigned int.

This is why sizeof('a') is sizeof(int) (4), not 1 — a char is promoted to int in most expressions.

Usual arithmetic conversions

When a binary operator has operands of different types, the compiler applies the usual arithmetic conversions to find a common type:

1. Apply the integer promotions to both operands. 2. If either operand is floating, convert to the wider floating type. 3. If both are integer (after promotions), apply the integer conversion rank rules: the operand of lower rank converts to the type of the higher rank, and if they have the same rank but different signedness, the signed operand converts to the unsigned operand's type (unless the unsigned type's values all fit in the signed type, in which case the unsigned converts to signed).

This last part is the classic trap: int and unsigned int have the same rank, so the int converts to unsigned int.

Syntax

Conversions are implicit, but they follow these rules whenever an operator needs a common type:

char c = 1;
short s = 2;
int i = 3;
unsigned int u = 4;

c + s;    /* both promoted to int */
i + u;    /* i converted to unsigned int */

Examples

The signed/unsigned trap

#include <stdio.h>

int main(void)
{
    int i = -1;
    unsigned int u = 1;

    if (i < u) {
        printf("i is less than u\n");
    } else {
        printf("i is NOT less than u\n");  /* this prints */
    }
    return 0;
}

Expected output:

i is NOT less than u

i is converted to unsigned int, so -1 becomes UINT_MAX (a huge positive number), which is greater than 1.

Integer promotion of char

#include <stdio.h>

int main(void)
{
    char c = 'A';
    printf("%zu\n", sizeof(c));    /* 1 */
    printf("%zu\n", sizeof(+c));   /* sizeof(int), because + promotes */
    return 0;
}

Expected output (typical): 1 then 4.

Conversion rank

int a = 1;
long b = 2;
long long c = 3;

a + b;   /* a converts to long */
b + c;   /* b converts to long long */

How It Works

The compiler inserts implicit conversions to bring operands to a common type, then performs the operation in that type. The result has that common type. This happens entirely at compile time; the generated code performs the actual extension/truncation instructions (e.g., sign-extend or zero-extend a value to the wider type).

Variations

Conversion rank

Integer types are ordered by conversion rank:

_Bool < char < short < int < long < long long

Unsigned types have the same rank as their signed counterparts. size_t, ptrdiff_t, and other typedefs are *aliases* of one of the standard integer types, so their rank follows the underlying type.

Sign extension vs. zero extension

high bits with the sign bit).

bits with zero).

is -1 on a two's-complement platform with 8-bit char.

Common Mistakes

to unsigned.

unsigned int).

Undefined Behavior

does not fit into a signed integer type — but conversion *to* a signed integer type when the value is out of range is implementation-defined or raises an implementation-defined signal, not UB; see note below).

operation (signed overflow) may.

Note: Conversion of an out-of-range value to a *signed* integer type is implementation-defined (or raises an implementation-defined signal) in C17; conversion to an *unsigned* integer type wraps modulo 2^N and is defined. VERIFIED

Portability

etc. are) are implementation-defined, so the same rule can produce different results on different platforms.

bugs.

Under the Hood

Sign extension and zero extension are single machine instructions on most architectures (e.g., movsx/movzx on x86). The compiler chooses which based on the signedness of the source type.

Practical Usage

comment why.

signed/unsigned mixing.

is signed and buffer_size is size_t.

Exercises

1. Demonstrate the -1 < 1u trap with a program and explain the conversion. 2. Write a program that shows sizeof(char), sizeof(+char), and the promoted type. 3. Explain the result of (int)(unsigned char)0xFF vs. (int)(signed char)0xFF on your platform. 4. Write a bounds-check function that correctly handles a signed length and an unsigned buffer size without the conversion trap.

Deep Challenge

For each expression, state the common type, the conversion performed, and the resulting value (assume int is 32-bit two's complement):

unsigned char a = 255;
signed char   b = -1;
int           c = -1;
unsigned int  d = 1;

a + b
b + c
c + d
a + d

Then verify your answers by compiling and running a program that prints the types and values (use <inttypes.h> or carefully chosen format specifiers).

References

conversions), §6.3.1.3 (signed/unsigned conversions).

Verification

implementation-defined. VERIFIED

verified.`

Progress

Concept checkboxes

Mastery levels

ConceptCurrent level (0–8)Target level
c.types.promotion06
c.types.uac06