C 語言表達式中的隱式類型轉換(Usual Arithmetic Conversions)是許多難以重現 Bug 的溫床。最經典案例來自《C 專家編程》:sizeof 回傳 unsigned 導致有號負數變巨大無號數,條件判斷永假。本文系統梳理 K&R C 與現代標準 (C99+) 的轉換規則、整型提升、有號/無號互動陷阱與防禦性寫法。
#include <stdio.h>
int array[] = {23,34,12,17,204,99,16};
#define TOTAL_ELEMENTS (sizeof(array)/sizeof(array[0])) /* unsigned int */
int main(void) {
int d = -1, x;
if (d <= TOTAL_ELEMENTS - 2) /* d 隱式轉 unsigned,-1 → UINT_MAX */
x = array[d+1]; /* 永不執行 */
return 0;
}
根因:sizeof 型別為 size_t (無號),d 為 int (有號)。比較時 d 提升為無號,-1 變 4294967295 (32 位),條件永假。
char/short → int;float → doubledouble → 另一轉 doublelong → 另一轉 longunsigned → 另一轉 unsignedchar、short、_Bool、位域 → int (若 int 能表示所有值) 或 unsigned intint 以上型別不變依序匹配第一條滿足者:
long double → 另一轉 long doubledouble → 另一轉 doublefloat → 另一轉 float (C99 允許保持 float 精度運算,但多數編譯器仍升 double)| 運算元 1 | 運算元 2 | 轉換結果 | 範例結果 |
|---|---|---|---|
signed long (-1L) | unsigned int (1U) | 1U → signed long | -1L < 1U 為真 |
signed long (-1L) | unsigned long (1UL) | -1L → unsigned long | -1L > 1UL 為真 (因 -1L 變巨大無號) |
int (-1) | unsigned int (1U) | -1 → unsigned int | -1 > 1U 為真 (經典陷阱) |
short (-1) | unsigned short (1U) | 雙雙提升為 int → 有號比較 | -1 < 1 為真 (因提升為 int) |
float 不一定升 doublefloat f1, f2; double d; f1 = d * f2; /* C99 允許編譯器以 float 精度計算 d*f2,若結果相同 */
實務上 MSVC/GCC/Clang 預設仍升 double,除非開啟 -ffast-math 或使用 _Float16/_Float32 (C23)。
| 場景 | 風險 | 建議寫法 |
|---|---|---|
迴圈索引與 sizeof/容器大小比較 | 有號負數變巨大無號 | for (size_t i = 0; i < n; ++i) 統一用 size_t |
介面函數參數為 size_t/unsigned | 呼叫端傳負數 | 內部斷言 assert(val >= 0) 或改簽名為有號 |
| 位運算與移位 | 負數右移實作定義 | 僅對無號型別位操作;有號用除法 |
| 字面常數 | 1 是 int,1U 是無號 | 明確後綴:1UL、1LL、1.0f |
| 編譯器警告 | 隱式轉換易漏檢 | -Wsign-conversion -Wsign-compare -Wconversion (GCC/Clang) |
sizeof、陣列大小、標準庫大小函數皆回傳無號