首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C >> 關於C

printf 格式化陷阱:浮點數傳給 %ld 會發生什麼?

     編輯:關於C

這是一道經典的 C 語言未定義行為(UB)題目:當 printf 格式字符串與實際參數類型不匹配時,程序行為完全不可預測。

問題代碼


float n1 = 3.0;
double n2 = 3.0;
long n3 = 2000000000;
long n4 = 1234567890;
long n5 = 250;
long n6 = 213;

printf("%ld %ld %ld %ld %ld %ld\n", n1, n2, n3, n4, n5, n6);

標準規定

  • %ld 對應 long int(有符號長整數)。
  • float 參數傳遞時會默認參數提升為 double。
  • 可變參數函數無類型檢查,運行時完全依賴格式字符串解析參數棧/寄存器佈局。
  • 類型不匹配 = 未定義行為 (UB):C 標準不保證任何具體結果,可能輸出亂碼、崩潰、甚至看起來「正常」但極不穩定。

典型運行結果分析(x86-64 System V ABI 為例)

前 6 個整數/指針參數通過寄存器傳遞:RDI, RSI, RDX, RCX, R8, R9;浮點參數通過 XMM0~XMM7。
調用 printf("%ld %ld %ld %ld %ld %ld", n1, n2, n3, n4, n5, n6) 時:

  • n1 (float→double) → XMM0
  • n2 (double) → XMM1
  • n3 (long) → RDI
  • n4 (long) → RSI
  • n5 (long) → RDX
  • n6 (long) → RCX

printf 解析 %ld 時從整數寄存器/棧讀取,但前兩個參數在 XMM 寄存器中!結果:

  • 前兩個 %ld 讀取的是殘留在整數寄存器中的垃圾值(或之前調用遺留值)。
  • 後四個 %ld 正確讀取 n3~n6。
  • n5、n6 輸出 250 和 213(正確),但前兩個是隨機數。

為什麼有時看起來「正常」?

  • x86 (32 位 cdecl):所有參數壓棧,float/double 佔 8 字節,long 佔 4 字節。棧佈局錯位導致後續參數讀取錯誤偏移,全亂。
  • 某些嵌入式 ABI:整數與浮點統一棧傳遞,恰好位模式被誤讀為相近整數(極不靠譜)。
  • 編譯器優化可能內聯 printf 並部分檢查,但標準不保證。

正確寫法


// 打印 float/double
printf("%f %f ", n1, n2);        // %f 適用於 float(提升為double) 和 double
printf("%e %e ", n1, n2);        // 科學計數法
printf("%g %g ", n1, n2);        // 自動選擇 %f/%e

// 打印 long
printf("%ld %ld %ld %ld ", n3, n4, n5, n6);

// 混合打印
printf("float:%f double:%f long:%ld %ld %ld %ld\n", n1, n2, n3, n4, n5, n6);

編譯器輔助檢查

  • GCC/Clang: -Wformat -Wformat=2(默認 -Wall 包含)會報警:warning: format ‘%ld’ expects argument of type ‘long int’, but argument 1 has type ‘double’
  • MSVC: /W4 開啟格式檢查。
  • 靜態分析工具(Cppcheck, Clang Static Analyzer, Coverity)能深度檢測。

總結

格式字符串與參數類型必須嚴格匹配。 這道題的「陷阱」在於誘導你關注具體輸出值,實則答案只有一個:**未定義行為,輸出不可信,代碼有缺陷**。養成開啟編譯器警告、使用靜態分析、必要時用 static_assert 或類型安全格式庫(fmtlib, iostream, std::format C++20)的習慣,從根源杜絕此類問題。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved