13.19 我怎樣才知道對於任意的 sprintf 調用需要多大的目標緩衝區? 怎樣才能避免 sprintf() 目標緩衝區溢出?

當用於 sprintf() 的格式串已知且相對簡單時, 你有時可以預測出緩衝區的大小。 如果格式串中包含一個或兩個 %s, 你可以數出固定字符的個數再加上對插入的 字符串的 strlen() 調用的返回值。對於整形, %d 輸出的字符數不會超過
	((sizeof(int) * CHAR_BIT + 2) / 3 + 1)  /* +1 for '-' */
CHAR_BIT 在 <limits.h> 中定義, 但是這個計算可能 有些過於保守了。它計算的是數字以八進制存儲需要的字節數; 十進制的存儲可 以保證使用同樣或更少的字節數。

當格式串更複雜或者在運行前未知的時候, 預測緩衝區大小會變得跟重新實現  sprintf 一樣困難, 而且會很容易出錯。有一種最後防線的技術, 就是 fprintf()  向一塊內存區或臨時文件輸出同樣的內容, 然後檢查 fprintf 的返回值或臨時文件 的大小, 但請參見問題 19.14, 並提防寫文件錯誤。

如果不能確保緩衝區足夠大, 你就不能調用 sprintf(), 以防緩衝區溢出後改寫 其它的內存區。如果格式串已知, 你可以用 %.Ns 控制 %s 擴展的長度, 或者使用 %.*s, 參見問題 12.9

要避免溢出問題, 你可以使用限制長度的 sprintf() 版本, 即 snprintf()。 這樣使用:

    snprintf(buf, bufsize, "You typed \"%s\"", answer);
snprintf() 在幾個 stdio 庫中已經提供好幾年了, 包括 GNU 和 4.4bsd。 在 C99 中已經被標準化了。

作為一個額外的好處, C99 的 snprintf() 提供了預測任意 sprintf() 調用所需的 緩衝區大小的方法。C99 的 snprintf() 返回它可能放到緩衝區的字符數, 而它又 可以用 0 作為緩衝區大小進行調用。因此

	nch = snprintf(NULL, 0, fmtstring, /* 其它參數 */ );
這樣的調用就可以預測出格式串擴展後所需要的字符數。

另一個 (非標準的) 選擇是 asprintf() 函數, 在 bsd 和 GNU 的 C 庫中都有提供, 它調用 malloc 為格式串分配空間, 並返回分配內存區的指針。這樣使用:

	char *buf;
	asprintf(&buf, "%d = %s", 42, "forty-two");
	/* 現在, buf 指向含有格式串的 malloc 的內存 */

參考資料: [C9X, Sec. 7.13.6.6]。

翻譯朱群英、孫雲, LaTeX2HTML 編譯 朱群英 (2005-06-23)