#include <stdio.h>
#include <string.h>
int main() {
char arr[1000];
for (int i = 0; i < 1000; i++) {
arr[i] = -1 - i;
}
printf("%d\n", strlen(arr)); // 輸出 255
return 0;
}
直覺預期可能是 1000 或 0,實際輸出卻是 255。這背後揭示了 char 型別有號性、補碼表示與溢位行為的深層機制。
C 標準規定 char 是否為有號(signed char)或無號(unsigned char)屬於實作定義行為。主流 x86/x64 編譯器(GCC、MSVC、Clang)預設視為 signed char,範圍 [-128, 127]。可透過 -funsigned-char 或 /J 改變預設行為。
有號整數採用二補碼表示:
-1 → 0xFF (1111 1111)-128 → 0x80 (1000 0000) 最小值127 → 0x7F (0111 1111) 最大值當計算結果超出範圍,發生有號整數溢位,屬於未定義行為。但主流編譯器採用「環繞」語意(截斷高位),即 -129 → 0x7F (127),-256 → 0x00 (0)。
| i | 計算值 -1-i | 補碼 (Hex) | 解釋為 signed char |
|---|---|---|---|
| 0 | -1 | 0xFF | -1 (非 '\0') |
| 127 | -128 | 0x80 | -128 (非 '\0') |
| 128 | -129 | 0x7F | 127 (非 '\0') |
| 255 | -256 | 0x00 | 0 ('\0') |
| 256 | -257 | 0xFF | -1 (循環重複) |
arr[255] 首次出現 '\0',strlen 以此為終止條件,故回傳 255。
錯誤:假設 char 為無號,直接存儲 0~255 數值。
正確:明確使用 uint8_t (C99 <stdint.h>) 或 unsigned char 表達位元組資料。
strlen、strcpy 等字串函式預期以 '\0' 結尾的字串。將數值陣列當字串處理,極易因資料中恰好出現 0 而提前截斷,或因無終止符導致緩衝區溢位。
雖然主流編譯器環繞,但標準不保證。涉及邊界運算時,應轉換為較寬整型(int、long)計算後再截斷存回,或使用 unsigned 型別明確定義環繞語意。
#include <stdio.h>
#include <stdint.h>
int main() {
uint8_t arr[1000]; // 明確無號 8-bit
for (int i = 0; i < 1000; i++) {
arr[i] = (uint8_t)(-1 - i); // 定義明確的模 256 環繞
}
// 不再使用 strlen,改用已知長度或自訂結構
printf("arr[255] = %u\n", arr[255]); // 輸出 0
return 0;
}
這個經典面試題揭示了三大 C 語言核心概念:型別有號性實作定義、補碼溢位環繞、字串終止符約定。實務開發中:
uint8_t / unsigned charchar + 確保 '\0' 終止char 有號性,編譯選項固定或使用標準整型理解底層位元模型,才能寫出可移植、無驚喜的系統級程式碼。