初學 C 語言時,scanf("%s", str) 和 gets(str) 是最常見的字串讀取方式,但它們各自有嚴重缺陷:前者遇空白截斷,後者極其危險且已被標準移除。本文深度對比三者行為,並給出現代 C 開發的最佳實踐。
| 函數 | 頭文件 | 能否讀取空白 | 緩衝區溢出保護 | 殘留換行符處理 | 標準狀態 |
|---|---|---|---|---|---|
scanf("%s") |
stdio.h | ❌ 遇空格/Tab/換行停止 | ❌ 無長度限制(需用 %99s) |
換行符留在 stdin | C89~C18 標準 |
gets() |
stdio.h | ✅ 讀整行(含空格) | ❌ 完全無保護,必導致溢出 | 吞掉換行符,不存入 | C11 移除,C99 棄用 |
fgets() |
stdio.h | ✅ 讀整行(含空格) | ✅ 指定最大讀取長度 | 保留換行符(若緩衝區夠大) | 推薦標準函數 |
char ch1[10], ch2[10];
scanf("%s", ch1); // 輸入 "asdfg\n",ch1="asdfg",\n 留在緩衝區
gets(ch2); // 直接讀到殘留的 \n,ch2=""(空字串)!
修正:scanf("%s", ch1); getchar(); // 吞掉殘留 \n 或改用 fgets 統一讀行再解析。
char buf[8];
gets(buf); // 輸入超過 7 字元直接覆蓋棧,程式崩潰或被攻擊
這是歷史上無數緩衝區溢出攻擊的溫床,嚴禁在任何生產代碼中使用 gets。
#define MAX_LINE 1024
char line[MAX_LINE];
if (fgets(line, sizeof(line), stdin)) {
// 1. 移除尾部換行符(若存在)
size_t len = strlen(line);
if (len > 0 && line[len - 1] == '\n') line[len - 1] = '\0';
// 2. 解析:sscanf / strtok / 手寫解析器
// sscanf(line, "%d %f %s", &a, &b, str);
}
fgets 保證不超過緩衝區。若必須用 scanf 讀單詞(不含空白),請永遠加寬度限制:
char buf[100];
scanf("%99s", buf); // 最多讀 99 字元,留 1 位給 \0
sscanf 或手寫解析器,是工業級 C 代碼讀取輸入的標準模式。