假設以下幾點:printf格式符號數
sizeof(char) = 1
sizeof(short) = 2
sizeof(int) = 4
sizeof(long) = 8
的printf
格式爲一個2字節符號數是%hd
,對於4字節的有符號數爲%d
,對於8字節符號數是%ld
,但是1字節有符號數字的正確格式是什麼?
假設以下幾點:printf格式符號數
sizeof(char) = 1
sizeof(short) = 2
sizeof(int) = 4
sizeof(long) = 8
的printf
格式爲一個2字節符號數是%hd
,對於4字節的有符號數爲%d
,對於8字節符號數是%ld
,但是1字節有符號數字的正確格式是什麼?
1字節有符號數的正確格式是什麼? 。
%hh
和您所選擇的整數轉換符(例如,%02hhX
見C11標準,§ 7.21.6.1p5:
hh
指定後續
d
,i
,o
,u
,x
或X
轉換說明符適用於帶符號的char或unsigned char參數(參數將已根據整數升級進行了升級,但打印前它的值應轉換爲帶符號的char或unsigned char);…
加括號的評論很重要。由於可變參數函數參數的整數提升(如printf
),函數從不會看到參數char
。許多程序員認爲這意味着沒有必要使用h
和hh
限定符。當然,你不會因爲將它們排除在外而造成不確定的行爲,並且大部分時間都會起作用。
但是,char
可能會被簽名,整數升級將保留其值,這將使其成爲一個有符號的整數。使用無符號格式(例如%02X
)打印帶符號的整數將顯示符號擴展F
。因此,如果您想使用無符號格式顯示帶符號的char
,則需要使用hh
告訴printf
整數類型的原始未擴展寬度。
在的情況下是不明確的,一個簡單的例子(但有爭議)例如:
/* Read the comments thread to this post; I'll remove
this note when I edit the outcome of the discussion into
the answer
*/
#include <stdio.h>
int main(void) {
char* s = "\u00d1"; /* Ñ */
for (char* p = s; *p; ++p) printf("%02X (%02hhX)\n", *p, *p);
return 0;
}
輸出:
$ ./a.out
FFFFFFC3 (C3)
FFFFFF91 (91)
在註釋螺紋,存在(或可能是)相當多的討論關於是否上述片段是未定義的行爲,因爲X
格式規範需要一個無符號參數,而char
參數我(至少在產生輸出結果的實現上)簽名。我認爲這個論點依賴於§ 7.12.6.1/p9:「如果任何參數不是相應轉換規範的正確類型,則行爲是未定義的。」
然而,在char
(和short
)整數類型的情況下,在參數列表中的表達被晉升爲int
或unsigned int
稱爲函數之前。(值得一提的是,在大多數架構,所有這三個字符類型將被提升到一個簽名int
;促進一個unsigned char
(或無符號char
)的一個unsigned int
只會發生在一個實現,其中sizeof(int) == 1
。)
所以上大多數體系結構中,將對%hx
或%hhx
格式轉換的參數進行簽名,並且這些不能是未定義的行爲,而不使用這些格式代碼就沒有意義。
此外,標準並沒有說fprintf
(和朋友)會以某種方式恢復原始表達。它所說的是,值「將在打印之前將轉換爲帶符號的字符或無符號字符」(§ 7.21.6.1/p5,上面引用,強調增加)。
將有符號值轉換爲無符號值不是未定義的。它甚至沒有指定或實現依賴。它簡單地包括(概念上)「重複地增加或減少一個比在新類型中可以表示的最大值更大的值,直到該值在新類型的範圍內」。 (§ 6.3.1.3/p2)
因此,有一個良好定義的過程來參數表達式轉換爲(可能簽名)int
參數,該值轉換爲unsigned char
良好定義的過程。因此,我認爲像上面提到的那樣的程序是完全明確的。
對於佐證,的fprintf
給定格式說明%c
的行爲被定義如下(§ 7.21.6.8/p8),加上強調:
的
int
參數轉換爲unsigned char
,並寫出結果字符。
如果一個人申請這使得上述程序未定義所提出的限制性解釋,那麼我相信一個將被迫還認爲:
void f(char c) {
printf("This is a '%c'.\n", c);
}
也是UB。然而,我認爲幾乎所有的C程序員都寫了類似的東西,而沒有再三思考。
問題的關鍵部分是§7.12.6.1/ p9(以及§7.12.6.1的其他部分)中「參數」的含義。 C++標準略微更精確;它指定如果參數受默認參數促銷的影響,「參數的值在調用之前轉換爲促銷類型」,我解釋這意味着在考慮呼叫時(例如呼叫fprintf
),這些論點現在是推廣價值。
我不認爲C實際上是不同的,至少在意圖。它使用類似「參數&hellips;被提升」的措詞,並且至少在一個地方「推廣後的論點」。此外,在可變參數函數(va_arg
宏,§ 7.16.1.1)的描述中,參數類型的約束被括號註釋爲「實際下一個參數的類型(根據默認參數促銷提升)」。
我會自由地認爲,所有這一切都是(a)細讀不夠精確的語言,(b)數舞蹈天使。但我沒有看到任何價值宣稱像使用%c
和char
參數的標準用法是「技術上」UB;這使UB的概念變得不明確,很難相信這種禁止是有意的,這使我相信這種解釋不是有意的。 (也許應該在編輯上加以糾正。)
「打印前它的值應該轉換爲帶符號的字符還是無符號的字符」的目的是什麼?這個額外的轉換完成了什麼? (另見我在這個問題下給Joachim的評論。) – 2015-02-07 22:10:35
@MikeNakis:我正在編輯這個問題來回答這個問題,希望有人想知道。讓我知道編輯是否有幫助。 – rici 2015-02-07 22:12:55
是的,它有幫助,謝謝! (當然,爲什麼我沒有想到它!) – 2015-02-07 22:13:58
僅供參考:'sizeof(char)'在規範中定義爲總是'1',不管'char'的實際大小。 – 2015-02-07 21:57:34
將它提升爲「int」並僅使用「%d」有什麼不妥? – 5gon12eder 2015-02-07 21:57:34
至於你的問題,你可能想閱讀例如[此參考資料](http://en.cppreference.com/w/c/io/fprintf)。 – 2015-02-07 21:58:08