[英]How to print types of unknown size like ino_t?
我經常遇到這樣的情況:我想用printf
打印整數類型的實現定義大小的值(比如ino_t
或time_t
)。 現在,我使用這樣的模式:
#include <inttypes.h>
ino_t ino; /* variable of unknown size */
printf("%" PRIuMAX, (uintmax_t)ino);
這種方法到目前為止有效,但它有一些缺點:
有更好的策略嗎?
#include <inttypes.h>
ino_t ino; /* variable of unknown size */
/* ... */
printf("%" PRIuMAX, (uintmax_t)ino);
這肯定會有效(有一些附帶條件;見下文),但我會用:
printf("%ju", (uintmax_t)ino);
j
長度修飾符
指定以下
d
,i
,o
,u
,x
或X
轉換說明符適用於intmax_t
或uintmax_t
參數; 或者后面的n
轉換說明符適用於指向intmax_t
參數的指針。
還有size_t
和ptrdiff_t
(以及它們對應的有符號/無符號類型)的z
和t
修飾符。
個人而言,我發現<inttypes.h>
定義的格式字符串宏難看並且難以記住,這就是為什么我更喜歡"%ju"
或"%jd"
。
正如您所提到的,知道類型(在本例中為ino_t
)是有符號還是無符號是有幫助的。 如果你碰巧不知道這一點,就有可能搞清楚:
#include <stdio.h>
#include <stdint.h>
#include <sys/types.h>
#define IS_SIGNED(type) ((type)-1 < (type)0)
#define DECIMAL_FORMAT(type) (IS_SIGNED(type) ? "%jd" : "%ju")
#define CONVERT_TO_MAX(type, value) \
(IS_SIGNED(type) ? (intmax_t)(value) : (uintmax_t)(value))
#define PRINT_VALUE(type, value) \
(printf(DECIMAL_FORMAT(type), CONVERT_TO_MAX(type, (value))))
int main(void) {
ino_t ino = 42;
PRINT_VALUE(ino_t, ino);
putchar('\n');
}
雖然這可能有點矯枉過正。 如果您確定類型的值小於64位,則可以將該值轉換為intmax_t
,並保留該值。 或者您可以使用uintmax_t
並為所有值獲得明確定義的結果,但打印-1
為18446744073709551615
(2 64 -1)可能會有點混亂。
所有這些只有在您的C實現支持<stdint.h>
和printf
的j
長度修飾符時才有效 - 即,如果它支持C99。 Microsoft ). 並非所有編譯器都這樣做( 微軟 )。 對於C90,最寬的整數類型是long
和unsigned long
,您可以轉換為那些並使用"%ld"
和/或"%lu"
。 理論上,您可以使用__STDC_VERSION__
預定義宏來測試C99合規性 - 盡管某些C99之前的編譯器可能仍然支持寬度大於long
且unsigned long
類型作為擴展。
整數類型的“大小”在這里不相關,但是它的值范圍。
顯然你已經嘗試過了,可以轉換為uintmax_t
和intmax_t
來輕松解決printf()
調用中的任何歧義。
簽名或無符號類型的問題可以通過簡單的方式解決:
x
是否具有有符號或無符號類型,只需驗證x
和-x
是否都是非負值。 例如:
if ( (x>=0) && (-x>=0) )
printf("x has unsigned type");
else
printf("x has signed type");
現在,我們可以編寫一些宏:
(已編輯:宏的名稱和表達已更改)
#include <inttypes.h>
#include <limits.h>
#define fits_unsigned_type(N) ( (N >= 0) && ( (-(N) >= 0) || ((N) <= INT_MAX) ) )
#define smartinteger_printf(N) \
(fits_unsigned_type(N)? printf("%ju",(uintmax_t)(N)): printf("%jd",(intmax_t) (N)) )
// ....
ino_t x = -3;
printf("The value is: ");
smartinteger_printf(x);
//.....
注意:當值為0時,上面的宏沒有很好地檢測到變量的有符號或無符號字符。但是在這種情況下一切都運行良好,因為0在有符號或無符號類型中具有相同的位表示。
第一個宏可用於檢測算術對象的基礎類型是否具有未編號類型。
此結果在第二個宏中用於選擇在屏幕上打印對象的方式。
第一次罰款:
char
和short
值在int
范圍內進行。 這相當於詢問該值是否在INT_MAX
0到INT_MAX
。 所以我已經將宏的名稱更改為fits_signed_type
。
此外,我已修改宏以考慮正int
值。
在大多數情況下,macro fits_unsigned_type
可以判斷對象是否具有無符號整數類型。
unsigned
。 -N
為正,則N具有unsigned
類型, -N
為負,但N在0到INT_MAX
的范圍內,則N
的類型可以是有signed
或unsigned
,但它適合int
的正值范圍,它適合uintmax_t
的范圍。 第二次罰款:
Ir似乎有解決同樣問題的方法。 我的方法考慮了值的范圍和整數提升規則,以使用printf()
生成正確的打印值。 另一方面,Grzegorz Szpetkowski的方法確定了直線形式的有符號字符 。 我喜歡這兩個。
由於您已經在使用C99標頭,因此可以根據sizeof(T)
和signed / unsigned檢查使用精確寬度格式說明符。 然而,這必須在預處理階段之后完成(很遺憾, ##
operator不能用於構造PRI令牌)。 這是一個想法:
#include <inttypes.h>
#define IS_SIGNED(T) (((T)-1) < 0) /* determines if integer type is signed */
...
const char *fs = NULL;
size_t bytes = sizeof(T);
if (IS_SIGNED(T))
switch (bytes) {
case 1: fs = PRId8; break;
case 2: fs = PRId16; break;
case 4: fs = PRId32; break;
case 8: fs = PRId64; break;
}
else
switch (bytes) {
case 1: fs = PRIu8; break;
case 2: fs = PRIu16; break;
case 4: fs = PRIu32; break;
case 8: fs = PRIu64; break;
}
使用此方法不再需要強制轉換,但是在將其傳遞給printf
之前必須手動構造格式字符串(即沒有自動字符串連接)。 這是一些工作示例:
#include <stdio.h>
#include <inttypes.h>
#define IS_SIGNED(T) (((T)-1) < 0)
/* using GCC extension: Statement Expr */
#define FMT_CREATE(T) ({ \
const char *fs = NULL; \
size_t bytes = sizeof(ino_t); \
\
if (IS_SIGNED(T)) \
switch (bytes) { \
case 1: fs = "%" PRId8; break; \
case 2: fs = "%" PRId16; break; \
case 4: fs = "%" PRId32; break; \
case 8: fs = "%" PRId64; break; \
} \
else \
switch (bytes) { \
case 1: fs = "%" PRIu8; break; \
case 2: fs = "%" PRIu16; break; \
case 4: fs = "%" PRIu32; break; \
case 8: fs = "%" PRIu64; break; \
} \
fs; \
})
int main(void) {
ino_t ino = 32;
printf(FMT_CREATE(ino_t), ino); putchar('\n');
return 0;
}
注意這需要一些Statement Expr的小技巧 ,但也可能有其他方式(這是“價格”通用)。
這是第二個版本,使用類似函數的宏不需要特定的編譯器擴展(不用擔心我也讀不出來):
#include <stdio.h>
#include <inttypes.h>
#define IS_SIGNED(T) (((T)-1) < 0)
#define S(T) (sizeof(T))
#define FMT_CREATE(T) \
(IS_SIGNED(T) \
? (S(T)==1?"%"PRId8:S(T)==2?"%"PRId16:S(T)==4?"%"PRId32:"%"PRId64) \
: (S(T)==1?"%"PRIu8:S(T)==2?"%"PRIu16:S(T)==4?"%"PRIu32:"%"PRIu64))
int main(void)
{
ino_t ino = 32;
printf(FMT_CREATE(ino_t), ino);
putchar('\n');
return 0;
}
請注意,條件運算符已經保持了共生性(因此它按預期從左到右進行評估)。
使用C11類型的通用宏,可以在編譯時構造格式字符串,例如:
#include <inttypes.h>
#include <limits.h>
#include <stdint.h>
#include <stdio.h>
#define PRI3(B,X,A) _Generic((X), \
unsigned char: B"%hhu"A, \
unsigned short: B"%hu"A, \
unsigned int: B"%u"A, \
unsigned long: B"%lu"A, \
unsigned long long: B"%llu"A, \
signed char: B"%hhd"A, \
short: B"%hd"A, \
int: B"%d"A, \
long: B"%ld"A, \
long long: B"%lld"A)
#define PRI(X) PRI3("",(X),"")
#define PRIFMT(B,X,A) PRI3(B,(X),A),(X)
int main () {
signed char sc = SCHAR_MIN;
unsigned char uc = UCHAR_MAX;
short ss = SHRT_MIN;
unsigned short us = USHRT_MAX;
int si = INT_MIN;
unsigned ui = UINT_MAX;
long sl = LONG_MIN;
unsigned long ul = ULONG_MAX;
long long sll = LLONG_MIN;
unsigned long long ull = ULLONG_MAX;
size_t z = SIZE_MAX;
intmax_t sj = INTMAX_MIN;
uintmax_t uj = UINTMAX_MAX;
(void) printf(PRIFMT("signed char : ", sc, "\n"));
(void) printf(PRIFMT("unsigned char : ", uc, "\n"));
(void) printf(PRIFMT("short : ", ss, "\n"));
(void) printf(PRIFMT("unsigned short : ", us, "\n"));
(void) printf(PRIFMT("int : ", si, "\n"));
(void) printf(PRIFMT("unsigned int : ", ui, "\n"));
(void) printf(PRIFMT("long : ", sl, "\n"));
(void) printf(PRIFMT("unsigned long : ", ul, "\n"));
(void) printf(PRIFMT("long long : ", sll, "\n"));
(void) printf(PRIFMT("unsigned long long: ", ull, "\n"));
(void) printf(PRIFMT("size_t : ", z, "\n"));
(void) printf(PRIFMT("intmax_t : ", sj, "\n"));
(void) printf(PRIFMT("uintmax_t : ", uj, "\n"));
}
但是存在一個潛在的問題:如果存在與列出的類型不同的類型(即,除了有signed
和unsigned
版本的char
, short
, int
, long
和long long
),這對這些類型不起作用。 也不可能將size_t
和intmax_t
等類型添加到類型通用宏“以防萬一”,因為如果它們是已經列出的類型之一的typedef
,則會導致錯誤。 我沒有指定default
情況,以便在沒有找到匹配類型時宏生成編譯時錯誤。
但是,如示例程序中所示, size_t
和intmax_t
在平台上工作正常,它們與列出的類型之一相同(例如, long
相同)。 類似地,如果例如long
和long long
,或long
和int
是相同類型,則沒有問題。 但是更安全的版本可能只是根據簽名轉換為intmax_t
或uintmax_t
(如其他答案中所示),並且僅使用那些選項創建類型通用宏...
一個美觀的問題是類型泛型宏在字符串文字周圍用括號擴展,防止以通常的方式連接相鄰的字符串文字。 這可以防止以下情況:
(void) printf("var = " PRI(var) "\n", var); // does not work!
因此, PRIFMT
宏包含前綴和后綴,用於打印單個變量的常見情況:
(void) printf(PRIFMT("var = ", var, "\n"));
(請注意,使用printf
支持的非整數類型擴展此宏會很簡單,例如double
, char *
...)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.