同一份 C 代碼在 VC6.0 運行正確,GCC 編譯卻輸出錯誤,典型原因是觸發了未定義行為 (UB),不同編譯器對 UB 的處理方式不同。本文分析提供的大整數除法代碼的問題。
char deno[1000] = {'\0'};
// ...
scanf("%s %d", deno, &num); // 讀入字符串到 deno
// ...
while (deno[i] != '\0') deno[i++] -= '0'; // 字符轉數字
deno[i] = '#'; // 結束標志
// ... 後續用 '#' 判斷結束
%s 不限制寬度,輸入超過 999 字符會導致緩衝區溢出(UB)。VC6.0 調試版可能在棧尾放置了保護頁,掩蓋了問題;GCC 直接崩潰或數據錯亂。deno 先存字符,又原地轉為數字,後又存 '#' 作為標志,語義混亂,極易越界。char 有無符號由編譯器決定。deno[i] -= '0' 若 char 是 signed 且字符大於 127 會溢出。num、deno 內容不確定。(gdb) p deno[0]
$2 = 0 '\000' // 輸入後 deno[0] 仍為 0,說明 scanf 失敗或緩衝區已損壞
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#define MAX_DIGITS 1000
int main() {
char str[MAX_DIGITS + 1];
int divisor;
// 1. 安全讀取
if (scanf("%1000s %d", str, &divisor) != 2) return 1;
if (divisor == 0) return -1;
// 2. 驗證輸入全為數字
for (int i = 0; str[i]; i++) {
if (!isdigit((unsigned char)str[i])) return 1;
}
// 3. 使用單獨的 int 數組存儲數字,避免字符/數字混用
int digits[MAX_DIGITS];
int len = 0;
for (int i = 0; str[i]; i++) digits[len++] = str[i] - '0';
// 4. 高精度除法(模擬手算)
int rem = 0;
for (int i = 0; i < len; i++) {
int cur = rem * 10 + digits[i];
digits[i] = cur / divisor;
rem = cur % divisor;
}
// 5. 輸出商(跳過前導零)
int i = 0;
while (i < len && digits[i] == 0) i++;
if (i == len) putchar('0');
for (; i < len; i++) putchar('0' + digits[i]);
printf(" %d\n", rem);
return 0;
}
| 特性 | VC6.0 (舊) | GCC (現代) |
|---|---|---|
| C 標準 | C89 + 微軟擴展 | C11/C17 严格模式 |
| 緩衝區溢出 | 調試版可能靜默截斷 | 觸發段錯誤或數據錯亂 |
| char 符號性 | 默認 signed | 視目標平台而定 (-funsigned-char) |
| 未初始化變量 | 調試版自動置 0 | 隨機值 |
-Wall -Wextra -pedantic -std=c11 暴露潛在問題。valgrind/AddressSanitizer 檢測內存錯誤。unsigned char 配合 ctype.h 函數。代碼若依賴未定義行為,換編譯器必出問題;遵循標準、開啟警告、工具檢測,是跨平台穩健的保障。