在C語言開發中,格式化輸出是最基礎也最常用的操作之一。標準庫提供了一系列函數,針對不同輸出目標(標準輸出、文件流、字符串緩衝區)以及不同參數傳遞方式(可變參數列表 vs va_list)進行了封裝。掌握它們的關係、返回值特性及常見陷阱,對編寫健壯、安全的代碼至關重要。
以下函數均聲明於 <stdio.h>(v* 系列需包含 <stdarg.h>):
#include <stdio.h> int printf(const char *format, ...); int fprintf(FILE *stream, const char *format, ...); int sprintf(char *str, const char *format, ...); int snprintf(char *str, size_t size, const char *format, ...); #include <stdarg.h> int vprintf(const char *format, va_list ap); int vfprintf(FILE *stream, const char *format, va_list ap); int vsprintf(char *str, const char *format, va_list ap); int vsnprintf(char *str, size_t size, const char *format, va_list ap);
所有函數均在格式化字符串控制下,將後續參數轉換為指定格式並輸出。格式化字符串決定了參數的轉換方式,例如:
printf("%02x", 100); /* 輸出十六進制:64 */
printf("%d", 100); /* 輸出十進制:100 */
printf()、vprintf()fprintf()、vfprintf()sprintf()、snprintf()、vsprintf()、vsnprintf()vprintf()、vfprintf()、vsprintf()、vsnprintf() 接受 va_list 參數,分別對應上述四類函數。調用後 va_list 狀態變為未定義。snprintf()、vsnprintf() 限制最大寫入字節數(含結束符 '\0'),防止緩衝區溢出。'\0')。snprintf()/vsnprintf():若輸出長度 ≥ size,字符串被截斷,返回值為理論完整長度(不含 '\0'),可用於判斷是否發生截斷。示例:
char buf[10]; int len = snprintf(buf, 10, "%s$", "1234567890123"); // len = 13 (理論長度), buf = "123456789" (實際存入9字符+'\0')
sprintf()/vsprintf(),改用 snprintf()/vsnprintf()sprintf() 不檢查緩衝區邊界,極易導致緩衝區溢出,引發安全漏洞或程序崩潰。除非能絕對確保數據長度不超過緩衝區,否則務必使用帶長度限制的 snprintf() 系列。
如 sprintf(buf, "%s append", buf);,雖然某些編譯器版本下能運行,但 C 標準明確規定:sprintf()、snprintf()、vsprintf()、vsnprintf() 的源目地址重疊屬於未定義行為。請使用臨時緩衝區或 memmove() 等安全方式。
printf(user_input); 極其危險。若輸入包含 %n,會導致 printf() 向內存寫入數據,引發崩潰或被利用構造任意內存寫入漏洞。
正確做法:printf("%s", user_input); 或 fputs(user_input, stdout);。
類型不匹配(如 %d 對應 long long)屬未定義行為。使用編譯器警告(-Wformat)並嚴肅對待。
snprintf()/vsnprintf() 進行字符串格式化。va_list 的封裝函數,調用 va_end() 前勿再使用該 va_list。熟練掌握這些細節,能讓你的 C 代碼更安全、更可靠。