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

C 語言進階指南 (一):整數型別轉換陷阱與記憶體分配最佳實踐

     編輯:關於C語言

第一部分:整數型別轉換 — 隱形殺手

經典陷阱:有號與無號比較


long i = -1;
if (i < sizeof(i)) {          // sizeof 回傳 size_t (無號)
    printf("OK\n");
} else {
    printf("Error\n");        // 輸出 Error!
}

原因分析

C 標準「慣用算術轉換」規則:

  1. 若一方為無號、且轉換等級 ≥ 另一方,則**有號轉為無號**。
  2. `sizeof` 回傳 `size_t` (無號),等級 ≥ `long`。
  3. `i` (-1) 轉為無號變成 `SIZE_MAX` (極大正數),比較失敗。

修正寫法


/* 方法 1:雙方同為有號 */
if ((long)i < (long)sizeof(i))

/* 方法 2:先檢查負數 */
if (i >= 0 && (size_t)i < sizeof(i))

/* 方法 3:用 intptr_t 統一有號指標大小 */
#include 
if (i < (intptr_t)sizeof(i))

固定寬度型別

為了可移植性,**永遠使用 ``**:

分類型別範例用途
精確寬度`int32_t`, `uint64_t`網路協定、檔案格式、硬體暫存器
最小寬度`int_least32_t`至少 N bits,編譯器挑最快
最快最小寬度`int_fast32_t`至少 N bits 且最快 (通常等於機器字長)

整數提升規則


uint16_t a = 65535, b = 1;
uint32_t sum = a + b;  // 32-bit 平台:a,b 提升為 int (16-bit 會提升為 int),結果 65536
                       // 16-bit 平台:a,b 提升為 unsigned int,結果 0 (溢位)

**結論**:跨平台代碼**不可假設 `int` 寬度**,關鍵運算明確轉型至固定寬度型別。

字元型別簽名陷阱


char c = 128;  // 有號 char 視為 -128,無號視為 128
printf("%d\n", c);  // x86 GCC 輸出 -128 (預設 signed char)
                    // ARM 某編譯器輸出 128 (預設 unsigned char)

**強制指定**:signed char / unsigned char,或編譯選項 `-fsigned-char` / `-funsigned-char`。

第二部分:動態記憶體分配 — 生命線

四大函數契約

函數行為關鍵點
`malloc(size)`分配未初始化記憶體size=0 行為未定義 (可能返回 NULL 或唯一指標)
`calloc(n, size)`分配並**歸零**自動計算總大小 n*size,避免溢位;適合陣列
`realloc(ptr, new_size)`調整大小,可能移動失敗返回 NULL,**原記憶體不釋放**;ptr=NULL 等同 malloc
`free(ptr)`釋放ptr=NULL 安全無操作;釋放後**必須置 NULL** 防雙重釋放

安全分配模式:防止整數溢位


void *safe_malloc(size_t n, size_t size) {
    if (n == 0 || size == 0) return NULL;
    if (n > SIZE_MAX / size) { errno = ENOMEM; return NULL; }  // 溢位檢查
    void *p = malloc(n * size);
    return p;
}

向量擴容實作範本 (教科書級)


typedef struct {
    int *data;
    size_t size;   // 當前元素數
    size_t capacity; // 已分配容量
} IntVec;

bool vec_push(IntVec *v, int val) {
    if (v->size == v->capacity) {
        size_t new_cap = v->capacity ? v->capacity * 2 : 4;
        if (new_cap > SIZE_MAX / sizeof(int)) return false;
        int *new_data = realloc(v->data, new_cap * sizeof(int));
        if (!new_data) return false;
        v->data = new_data;
        v->capacity = new_cap;
    }
    v->data[v->size++] = val;
    return true;
}

void vec_free(IntVec *v) {
    free(v->data);
    v->data = NULL;
    v->size = v->capacity = 0;
}

避免記憶體洩漏的六大鐵律

  1. 啟動時一次性分配:長期運行服務啟動階段分配所有池,運行期不再 `malloc`/`free`。
  2. 變長陣列 (VLA) 用於函數內臨時緩衝:C99 VLA 分配在堆疊,函數返回自動釋放,**但限制大小 (< 100KB) 防堆疊溢位**。
  3. 手動引用計數:共享物件建立 `retain`/`release`,計數歸零自動 `free`。
  4. 記憶體池:按生命週期劃分池 (請求池、連線池、會話池),階段結束整池釋放,消除碎片。
  5. 資料結構擁有權明確:鏈表節點由表頭擁有,樹節點由父擁有,**遍歷釋放**避免遺漏。
  6. 保守式垃圾回收 (Boehm GC):C 語言可選擇引入 GC,適合長期運行、分配模式複雜的專案。

常見錯誤速查

錯誤後果修正
`free` 後未置 NULL野指標、二次釋放崩潰`free(p); p = NULL;`
`realloc` 失敗未保留舊指標記憶體洩漏`new = realloc(p, sz); if (!new) { /* p 仍有效 */ } else p = new;`
釋放非堆記憶體 (堆疊/全域/字面常數)未定義行為、崩潰只 `free` 來自 `malloc`/`calloc`/`realloc` 的指標
跨 DLL 分配/釋放不匹配堆破壞分配者負責釋放,提供 `xxx_free` API

結語

整數轉換陷阱源於 **C 的隱式轉換規則 + 平台相關型別寬度**;記憶體管理痛點在於 **手動擁有權 + 無 GC**。掌握固定寬度型別、顯式轉型、溢位檢查、RAII 風格封裝、記憶體池、引用計數,能消滅 95% 的整數與記憶體 Bug。下一部分將探討陣列指標轉換、顯式記憶體函數、Interpositioning 與向量化。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved