未定義行為與安全

為什麼 gets 與無界 strcpy 危險

有些工具危險,不是因為你可能誤用,而是因為它根本「無法」被安全地使用。在 C 的標準函式庫裡,少數幾個老舊的字串函式就屬於這類:它們的設計本身就會寫入「呼叫端無法控制」其數量的資料,進到一個「固定」大小的緩衝區。這個不對稱,使得只要輸入夠大,緩衝區溢位就不是風險、而幾乎是必然。

最糟的元兇是 gets()。它從標準輸入讀一行進緩衝區,卻沒有任何參數表示緩衝區大小,所以不管輸入多大,它都一路寫到遇見換行為止。沒有任何可能的緩衝區大到足以對任意輸入安全,這就是為什麼 gets() 在 C11 被正式從 C 標準中移除——它是委員會少見地直接刪掉的函式。strcpy(dst, src) 在實務上幾乎一樣糟:它從 src 一路複製字元直到遇見結尾字元,完全不管 dst 多大,所以太長的 src 會讓 dst 溢位。strcat 在附加時有同樣缺陷。標準的「n」變體接受一個大小,較安全:snprintf 是建議用來格式化進緩衝區的方式,因為它絕不寫超過你給的大小、並總是補上結尾字元。strncpy 也接受大小,但帶著自己的警告:如果來源剛好和上限一樣長,它「不會」補上結尾的空字元,留給你一個未結尾的字串,之後的讀取會衝過頭——所以連「安全」的函式都需要小心。

為什麼重要:這些函式不是理論上的遺物;它們出現在教學、舊程式碼、與隨手寫的腳本裡,背後牽連著非常多的歷史性安全事件。實用的規則很直白:永遠別呼叫 gets()(改用帶明確大小的 fgets,或 getline);格式化進緩衝區時優先用 snprintf;把 strcpy 與 strcat 當成等著發生的溢位,除非你已證明目的地夠大;而當你用 strncpy 時,請自己補上結尾。底下真正的教訓是:目的地的大小必須隨目的地一起傳遞——一個不接受大小的函式,不可能安全。

char buf[64]; gets(buf); /* 絕不:沒有大小;已從 C11 移除 */ strcpy(buf, untrusted); /* 若 untrusted 超過 63 字元就溢位 */ /* 較安全的替代 */ fgets(buf, sizeof buf, stdin); /* 有界的整行讀取 */ snprintf(buf, sizeof buf, "%s", src); /* 有界、總是補結尾 */

gets() 與無界 strcpy 不接受目的地大小、無法被弄安全。fgets 與 snprintf 接受大小;請優先使用它們。

strncpy 比 strcpy 安全,但「不會」自動補結尾:若來源剛好填滿緩衝區,就不會寫入結尾字元,留給你一個未結尾的字串。請自己補上結尾,或乾脆優先用 snprintf。

又稱
unsafe string functionsgets()unbounded strcpy危險字串函式