首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C 語言細節探討(20):有符號與無符號轉換規則及整數溢出行為分析

     編輯:關於C語言

有符號/無符號轉換與溢出深度解析

C 語言整數類型轉換規則是 Bug 溫床。本文通過代碼實驗演示轉換規則、溢出行為及補碼表示特性。

實驗代碼與現象解讀

#include 
#include 

int main() {
    // 1. 整數字面值溢出
    int i = 200 * 300 * 400 * 500; // 12,000,000,000 > INT_MAX(2,147,483,647)
    printf("i = %d\n", i); // 輸出負數 (補碼截斷)

    // 2. 浮點數精度陷阱
    float j1 = (3.14 + 1e20) - 1e20; // 1e20 精度吞沒 3.14 → 0.0
    float j2 = 3.14 + (1e20 - 1e20); // 結合順序改變 → 3.14
    printf("j1 = %f, j2 = %f\n", j1, j2);

    // 3. 無符號最大值與 -1 補碼相同
    unsigned int u = UINT_MAX; // 4294967295 (0xFFFFFFFF)
    int tu = (int)u;           // -1 (補碼 0xFFFFFFFF)
    printf("u = %u, tu = %d\n", u, tu); // 輸出: u = 4294967295, tu = -1

    // 4. 有符號轉無符號:負數變大正數
    short v = -12345;
    unsigned short uv = (unsigned short)v; // 位模式不變: 0xCFC7
    printf("v = %d, uv = %u\n", v, uv);   // uv = 53191 (65536 - 12345)

    // 5. 表達式隱式轉換陷阱
    // 若一邊無符號,另一邊有符號 → 有符號隱式轉無符號
    // -1 < 0U  →  -1 轉為 4294967295U,比較結果為假 (0) ❌
    
    // 6. 短整型溢出行為 (補碼環細)
    short a = -65536; // 0x0000 (截斷)
    short b = -1;     // 0xFFFF
    printf("a+b = %d\n", a+b); // 0xFFFF + 0x0000 = 0xFFFF = -1
    return 0;
}

核心規則總結

1. 整數提升與常規算術轉換

  • 表達式中出現 unsigned 類型 → **整個表達式提升為無符號類型** 進行計算/比較。
  • 這導致 -1 > 0U 為真 (因 -1 變為巨大無符號數)。
  • 比較有符號與無符號時,編譯器警告 -Wsign-compare (在 -Wall 中)。

2. 有符號轉無符號 (位模式不變)

公式:U(x) = (x >= 0) ? x : x + 2^w (w 為目標類型位寬)。

  • 負數 → 對應巨大正數 (如 short -1 → unsigned short 65535)。
  • 非負數 → 數值不變。

3. 無符號轉有符號 (位模式不變)

公式:T(x) = (x < 2^{w-1}) ? x : x - 2^w。

  • 值在有符號範圍內 → 數值不變。
  • 值超出範圍 (高位為 1) → 解釋為負數 (如 UINT_MAX → -1)。

4. 有符號整數溢出 = 未定義行為 (UB)

  • C 標準規定:有符號溢出行為**未定義**。編譯器可假設不發生溢出進行優化 (如 x+1 > x 恆真)。
  • 實際上主流編譯器 (GCC/Clang/MSVC) 實現為**補碼環繞** (如實驗 6),但**不可依賴**。
  • 無符號溢出**定義為模 2^w 環繞** (安全可依賴)。

5. 溢出檢測技巧 (無 UB)

// 檢測 a + b 是否溢出 (有符號 int)
int safe_add(int a, int b, int *result) {
    if (b > 0 && a > INT_MAX - b) return 0; // 正溢出
    if (b < 0 && a < INT_MIN - b) return 0; // 負溢出
    *result = a + b;
    return 1;
}

最佳實踐

  1. 盡量使用**無符號類型**處理位操作、內存大小、索引 (size_t)。
  2. 避免有符號/無符號混合比較。必要時顯式轉換:(unsigned)a < b 或 (int)b > a。
  3. 溢出檢測用**邊界判斷** (如上),而非依賴溢出後結果。
  4. 開啟 -fwrapv (GCC/Clang) 強制有符號溢出環繞語義,若代碼依賴此行為 (如某些哈希算法)。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved