printf 是 C 語言最基礎的輸出函數,其變參機制與格式解析隱含關鍵底層知識。本文從轉換說明一一對應、參數棧佈局、類型不匹配後果、返回值應用四個維度徹底講透。
// 錯誤:參數少於格式符
printf("a=%d, b=%d", a); // 未定義行為:讀取棧垃圾作為 b
// 錯誤:參數多於格式符
printf("a=%d", a, b); // b 被忽略 (無害但有警告)
// 正確:嚴格一一對應
printf("a=%d, b=%d", a, b);
編譯器檢查:-Wformat (含於 -Wall) 可捕獲不匹配,**務必開啟**。
C 語言參數**右到左**壓棧,調用者清棧。printf 通過格式串解析決定**如何從棧中取數據** (大小、有符號/無符號)。
float n1 = 3.0;
double n2 = 3.0;
long n3 = 1234567890;
long n4 = 2000000000;
printf("%f %f\n", n1, n2); // 正確:n1 提升為 double (8字節),n2 本身 double
printf("%ld %ld\n", n3, n4); // 正確:各 4/8 字節 (視長度)
printf("%ld %ld %ld %ld\n", n1, n2, n3, n4); // 災難!
壓棧順序 (右到左): n4 (long, 8B) → 棧頂 n3 (long, 8B) n2 (double, 8B) ← float n1 自動提升為 double (8B) n1 (float→double, 8B) 格式串指針 (8B) printf 解析 "%ld %ld %ld %ld": 第1個 %ld → 讀取 8B → 得到 n1 的**前 8 字節** (實為 n1 提升後的 double 全部) 第2個 %ld → 讀取 8B → 得到 n1 的**後 8 字節** (實為棧上殘留/下一參數開頭) 第3個 %ld → 讀取 8B → 得到 n2 的前 8 字節 第4個 %ld → 讀取 8B → 得到 n2 的後 8 字節 n3, n4 根本沒被讀到!
核心結論:printf **完全信任格式串**,按格式符大小從棧取數據。**實際參數類型決定棧佈局,格式符決定解讀方式**,兩者必須一致,否則輸出亂碼甚至崩潰。
| 類型 | printf 格式 | scanf 格式 | 備註 |
|---|---|---|---|
int | %d / %i | %d | |
unsigned int | %u | %u | |
long | %ld | %ld | |
long long | %lld | %lld | |
float | %f / %e / %g | %f | 傳遞時提升為 double |
double | %f / %e / %g | %lf | scanf 必須 %lf |
long double | %Lf / %Le / %Lg | %Lf | |
char (字符) | %c | %c | |
char* (字符串) | %s | %s | |
void* (指針) | %p | %p | |
size_t | %zu (C99) | %zu | |
ptrdiff_t | %td (C99) | %td | |
intmax_t | %jd (C99) | %jd |
int printf(const char *format, ...);
%[flags][width][.precision][length]specifier
flags: - (左對齊) + (符號) space (正數前空格) 0 (填0) # (替代形式)
width: 最小字段寬度 (不足填充,* 從參數獲取)
.prec: 整數最小位數 / 浮點小數位數 / 字符串最大字符數 (* 從參數獲取)
length: hh (char) h (short) l (long/double) ll (long long) L (long double) z (size_t) t (ptrdiff_t) j (intmax_t)
printf("%+010d\n", 42); // +000000042
printf("%-10s|\n", "hi"); // "hi |"
printf("%.2f\n", 3.14159); // 3.14
printf("%.*f\n", 3, 3.14159); // 3.142 (精度從參數獲取)
printf("%p\n", (void*)main); // 0x4005b0
snprintf(buf, size, fmt, ...):限制輸出長度,**防緩衝區溢出**,返回**本應輸出的長度** (可能 > size)。asprintf(&str, fmt, ...) (GNU擴展):自動 malloc 緩衝區,返回長度,調用者 free。fprintf(stderr, ...):錯誤輸出無緩衝,即時顯示。dprintf(fd, fmt, ...) (POSIX):直接寫文件描述符。-Wformat -Werror。printf 用 %f/%e/%g 通用,**scanf 必須區分 %f(float*) / %lf(double*)**。%p 配合 (void*) 轉型。%lld/%zu/%jd,勿用 %d/%ld 假設長度。printf(user_str) → printf("%s", user_str)。snprintf,檢查返回值判斷截斷。printf 看似簡單,實則是「格式串解析器 + 棧讀取器」。理解其參數棧佈局由實際類型決定,解讀方式由格式符決定這一核心機制,配合嚴格的編譯器檢查與現代安全替代函數,即可寫出正確、安全、可移植的輸出代碼。