C 类型转换 — 整型提升与算术转换
整型提升(Integer Promotion)
任何 rank 低于 int 的类型,在表达式中先自动提升为 int(或 unsigned int)。
触发时机:
- 算术运算、比较运算、位运算的操作数
- 函数调用的参数(无原型时还要受 default argument promotion 再处理一次)
char a = 10;
short b = 20;
// a + b 实际按 int + int 计算,结果类型为 int
char是否有符号由实现决定 — 嵌入式常见unsigned char,x86 上通常signed char。 提升时若原始类型的所有值都能放进int,提升到int;否则提升到unsigned int。
寻常算术转换(Usual Arithmetic Conversions)
二元运算的两个操作数类型不同时,把”小”的一方转为”大”的一方的类型。
整数转换 Rank(由低到高)
| Rank | 类型 |
|---|---|
| 1 | _Bool |
| 2 | char, signed char, unsigned char |
| 3 | short, unsigned short |
| 4 | int, unsigned int |
| 5 | long, unsigned long |
| 6 | long long, unsigned long long |
转换规则(简化)
- 先做整型提升(低于
int的提到int/unsigned int) - 若类型仍不同,rank 低的向 rank 高的转
- 关键 — unsigned “赢”:同 rank 的 signed 与 unsigned 相遇,signed → unsigned
int a = -1;
unsigned int b = 1;
if (a > b) // 成立!因为 a 被转为 unsigned int,-1 → 0xFFFFFFFF(4294967295)
...这条规则不限于同 rank:当 unsigned 操作数的 rank ≥ signed 操作数的 rank,signed 被转为 unsigned。
int a = -5;
unsigned short b = 10; // 先提升为 int → 10,但...
int c = -5;
unsigned int d = 10;
// c + d → c 转为 unsigned int,-5 变成 0xFFFFFFFB,加法结果溢出🔴 翻车场景
1. 有符号减无符号 → 永远 ≥ 0
unsigned int len = 5;
int data_len = 3;
if (data_len - len > 0) // ❌ 你以为为 false,实际为 true
if (data_len > len) // ✅ 直接比,两边按 unsigned 比 → falsedata_len - len 的结果是无符号数,不可能 < 0。
2. sizeof 返回 size_t(unsigned)
int a[10];
for (int i = 0; i < sizeof(a) / sizeof(a[0]) - 1; i++) // ⚠️ 混合了 int 和 size_tsizeof 返回 size_t(unsigned long),比较时 i 被转为 size_t。这里不会出错,但换一种写法就翻车:
for (size_t i = 9; i >= 0; i--) // ❌ 死循环!i 永远 ≥ 0size_t 是无符号的,i == 0 时 i-- 变成 SIZE_MAX,仍然 ≥ 0。
3. 循环边界用 unsigned 导致死循环
// ❌ 死循环
for (unsigned int i = n; i >= 0; i--)
...
// ✅ 修正
for (int i = n; i >= 0; i--)
...
// 或用后判法:
for (unsigned int i = n + 1; i-- > 0; )
...4. 位运算结果默认 unsigned
unsigned char x = 0xFF;
// x 先整型提升为 int(值 255),不是 unsigned int
// 0xFF << 24 在 int 为 32 位的系统上可能溢出 → 未定义行为位运算操作数建议显式用 uint32_t 等定宽类型。
规避原则
| 场景 | 做法 |
|---|---|
| 比较、运算中不用无符号 | 能用 int 就用 int,除非真的需要模 2ⁿ 回绕语义 |
| 必须用无符号时 | 用 uint32_t 等定宽类型,明确意图 |
| 混用时 | 显式转换:if ((int)u < 0) 而不是等隐式转换 |
| 循环 | 绝不写 for (unsigned i = n; i >= 0; i--) |
| 位运算 | 统一用 uint8/16/32_t,避免提升意外 |