簡體   English   中英

printf 的 h 和 hh 修飾符的用途是什么?

[英]What is the purpose of the h and hh modifiers for printf?

除了%hn%hhn (其中hhh指定指向對象的大小)之外, printf格式說明符的hhh修飾符有什么意義?

由於標准要求應用於可變參數函數的默認提升,不可能將charshort (或其任何有符號/無符號變體)類型的參數傳遞給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 參數的指針。

如果參數實際上是shortunsigned short類型,則提升為int后轉換回shortunsigned short將產生與提升為int相同的,而無需任何轉換回。 因此,對於類型為shortunsigned short%d%u等應該給出與%hd%hu等相同的結果(對於char類型和hh也是如此)。

據我所知, hhh修飾符可能有用的唯一情況是當參數傳遞一個int超出shortunsigned 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 longshort將用f填充。 是相同的,但它確實在視覺上誤導了字段的大小,這意味着大量的范圍根本不存在。

%hx截斷顯示的表示以避免這種填充,正如您從實際用例中得出的結論一樣。

printf的行為在傳遞超出short范圍的int時未定義,該int應該打印為short ,但迄今為止最簡單的實現只是通過原始向下轉換丟棄高位,因此雖然規范不需要任何特定行為,幾乎任何理智的實現都會執行截斷。 不過,通常有更好的方法來做到這一點。

如果 printf 不是填充值或顯示有符號值的無符號表示,則%h不是很有用。

我能想到的唯一用途是傳遞unsigned shortunsigned char並使用%x轉換說明符。 你不能簡單地使用一個空的%x - 該值可能被提升為int而不是unsigned int ,然后你有未定義的行為。

您的替代方法是將參數顯式轉換為unsigned 或者使用帶有裸參數的%hx / %hhx

printf()等的可變參數使用默認轉換自動提升,因此任何shortchar值在傳遞給函數時都會提升為int

在沒有hhh修飾符的情況下,您必須屏蔽傳遞的值才能可靠地獲得正確的行為。 使用修飾符,您不再需要屏蔽值; 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 的字符數組大小。 這也給出了更准確的描述

https://github.com/Mellanox/libvma/commit/b5cb1e34a04b40427d195b14763e462a0a705d23#diff-6258d0a11a435aa372068037fe161d24

我同意你的看法,這不是絕對必要的,因此僅憑這個原因在 C 庫函數中是不好的:)

不同標志的對稱性可能“很好”,但它通常會適得其反,因為它隱藏了“轉換為int ”規則。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM