[英]What is the purpose of the h and hh modifiers for printf?
除了%hn
和%hhn
(其中h
或hh
指定指向對象的大小)之外, printf
格式說明符的h
和hh
修飾符有什么意義?
由於標准要求應用於可變參數函數的默認提升,不可能將char
或short
(或其任何有符號/無符號變體)類型的參數傳遞給printf
。
根據 7.19.6.1(7), h
修飾符:
指定后面的 d、i、o、u、x 或 X 轉換說明符適用於 short int 或 unsigned short int 參數(該參數將根據整數提升進行提升,但其值應轉換為 short int或 unsigned short int 打印前); 或者后面的 n 轉換說明符適用於指向 short int 參數的指針。
如果參數實際上是short
或unsigned short
類型,則提升為int
后轉換回short
或unsigned short
將產生與提升為int
相同的值,而無需任何轉換回。 因此,對於類型為short
或unsigned short
, %d
、 %u
等應該給出與%hd
、 %hu
等相同的結果(對於char
類型和hh
也是如此)。
據我所知, h
或hh
修飾符可能有用的唯一情況是當參數傳遞一個int
超出short
或unsigned short
范圍時,例如
printf("%hu", 0x10000);
但我的理解是,像這樣傳遞錯誤的類型會導致未定義的行為,因此您不能期望它打印 0。
我見過的一個真實案例是這樣的代碼:
char c = 0xf0;
printf("%hhx", c);
盡管實現具有帶符號的純char
類型,但作者希望它打印f0
(在這種情況下, printf("%x", c)
將打印fffffff0
或類似的)。 但這種期望有根據嗎?
(注意:發生的事情是原始類型是char
,它被提升為int
並轉換回unsigned char
而不是char
,從而改變了打印的值。但是標准是否指定了這種行為,或者它是一個實現損壞的軟件可能依賴的細節?)
一個可能的原因:為了與格式化輸入函數中使用這些修飾符的對稱性? 我知道這不是絕對必要的,但也許可以看到它的價值?
盡管他們在C99 基本原理文檔中沒有提到“h”和“hh”修飾符的對稱性的重要性,但委員會確實提到它是考慮為什么fscanf()
支持“%p”轉換說明符(即使這對 C99 來說並不新鮮——“%p”支持在 C90 中):
使用 %p 的輸入指針轉換被添加到 C89,盡管它顯然是有風險的,為了與 fprintf 對稱。
在有關fprintf()
的部分中,C99 基本原理文檔確實討論了添加了“hh”,但只是將讀者fscanf()
部分:
%hh 和 %ll 長度修飾符是在 C99 中添加的(參見 §7.19.6.2)。
我知道這是一個微不足道的話題,但無論如何我都是在猜測,所以我想我會給出任何可能的論點。
此外,為了完整起見,“h”修飾符在原始 C89 標准中 - 即使由於廣泛的現有使用而並非絕對必要,即使可能沒有使用修飾符的技術要求,它也可能存在.
在%...x
模式下,所有值都被解釋為無符號。 因此,負數被打印為它們的無符號轉換。 在大多數處理器使用的 2 的補碼算法中,有符號負數與其無符號正等價物之間的位模式沒有區別,后者由模數算法定義(將字段的最大值加一到負數,根據符合 C99 標准)。 許多軟件——尤其是最有可能使用%x
的調試代碼——都默默地假設有符號負值的位表示和它的無符號轉換是相同的,這僅在 2 的補碼機上才成立。
這個轉換的機制是這樣的,值的十六進制表示總是暗示,可能不准確,一個數字已經以 2 的補碼呈現,只要它沒有達到不同整數表示具有不同范圍的邊緣條件。 這甚至適用於不使用全 0 的二進制模式表示值 0 的算術表示。
因此,在任何機器上,由於促銷中的隱式符號擴展( printf
將打印),以十六進制顯示為unsigned long
負short
將用f
填充。 該值是相同的,但它確實在視覺上誤導了字段的大小,這意味着大量的范圍根本不存在。
%hx
截斷顯示的表示以避免這種填充,正如您從實際用例中得出的結論一樣。
printf
的行為在傳遞超出short
范圍的int
時未定義,該int
應該打印為short
,但迄今為止最簡單的實現只是通過原始向下轉換丟棄高位,因此雖然規范不需要任何特定行為,幾乎任何理智的實現都會執行截斷。 不過,通常有更好的方法來做到這一點。
如果 printf 不是填充值或顯示有符號值的無符號表示,則%h
不是很有用。
我能想到的唯一用途是傳遞unsigned short
或unsigned char
並使用%x
轉換說明符。 你不能簡單地使用一個空的%x
- 該值可能被提升為int
而不是unsigned int
,然后你有未定義的行為。
您的替代方法是將參數顯式轉換為unsigned
; 或者使用帶有裸參數的%hx
/ %hhx
。
printf()
等的可變參數使用默認轉換自動提升,因此任何short
或char
值在傳遞給函數時都會提升為int
。
在沒有h
或hh
修飾符的情況下,您必須屏蔽傳遞的值才能可靠地獲得正確的行為。 使用修飾符,您不再需要屏蔽值; printf()
實現正確地完成了這項工作。
具體來說,對於%hx
格式, printf()
的代碼可以執行以下操作:
va_list args;
va_start(args, format);
...
int i = va_arg(args, int);
unsigned short s = (unsigned short)i;
...print s correctly, as 4 hex digits maximum
...even on a machine with 64-bit `int`!
我很高興地假設short
是一個 16 位的數量; 當然,該標准實際上並不能保證這一點。
我發現在將無符號字符格式化為十六進制時避免強制轉換很有用:
sprintf_s(tmpBuf, 3, "%2.2hhx", *(CEKey + i));
這是一個次要的編碼便利,看起來比多次轉換(IMO)更干凈。
另一個方便的地方是 snprintf 大小檢查。 gcc7 在使用 snprintf 時添加了大小檢查,因此這將失敗
char arr[4];
char x='r';
snprintf(arr,sizeof(arr),"%d",r);
因此,在格式化字符時使用 %d 時,它會強制您使用更大的字符
這是一個提交,顯示了這些修復,而不是增加他們將 %d 更改為 %h 的字符數組大小。 這也給出了更准確的描述
我同意你的看法,這不是絕對必要的,因此僅憑這個原因在 C 庫函數中是不好的:)
不同標志的對稱性可能“很好”,但它通常會適得其反,因為它隱藏了“轉換為int
”規則。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.