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
c.core.3— integer types and ranges.c.core.8— expressions.
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
- Converting a signed value to a wider signed type sign-extends (fills
high bits with the sign bit).
- Converting an unsigned value to a wider type zero-extends (fills high
bits with zero).
- This is why
(int)(unsigned char)0xFFis 255, but(int)(signed char)0xFF
is -1 on a two's-complement platform with 8-bit char.
Common Mistakes
- Comparing signed and unsigned without realizing the signed operand converts
to unsigned.
- Assuming
charis promoted tounsigned char(it promotes tointor
unsigned int).
- Assuming
sizeofof a promoted value equals the original size. - Forgetting that
-1converted tounsignedis a huge number.
Undefined Behavior
- Signed overflow after a conversion can be UB (e.g., converting a value that
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).
- The usual arithmetic conversions themselves do not cause UB; the resulting
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
- The rules are standard, but the *underlying sizes* (how wide
int,long,
etc. are) are implementation-defined, so the same rule can produce different results on different platforms.
size_tis unsigned; mixing it with signed values is a frequent source of
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
- When comparing signed and unsigned, cast explicitly to the intended type and
comment why.
- Use
size_tconsistently for sizes and indices to avoid accidental
signed/unsigned mixing.
- Be especially careful in bounds checks like
if (n < buffer_size)wheren
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).
Related Concepts
c.types.int— integer types and ranges.c.sec.2— integer overflow/truncation in security.c.ops.sizeof— sizeof and promotion.
References
- ISO/IEC 9899:2018 §6.3.1.1 (integer promotions), §6.3.1.8 (usual arithmetic
conversions), §6.3.1.3 (signed/unsigned conversions).
Verification
- Integer promotion to
int/unsigned int.VERIFIED - Usual arithmetic conversions.
VERIFIED - Signed→unsigned conversion wraps; unsigned→signed out-of-range is
implementation-defined. VERIFIED
- No example was executed during generation unless noted. `Execution not
verified.`
Progress
- [ ] Read
- [ ] Understand
- [ ] Complete examples
- [ ] Complete exercises
- [ ] Complete deep challenge
Concept checkboxes
- [ ] Integer promotions
- [ ] Usual arithmetic conversions
- [ ] Conversion rank
- [ ] Signed/unsigned comparison trap
- [ ] Sign extension vs. zero extension
Mastery levels
| Concept | Current level (0–8) | Target level |
|---|---|---|
| c.types.promotion | 0 | 6 |
| c.types.uac | 0 | 6 |