簡體   English   中英

如何打印像ino_t這樣的未知大小的類型?

[英]How to print types of unknown size like ino_t?

我經常遇到這樣的情況:我想用printf打印整數類型的實現定義大小的值(比如ino_ttime_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長度修飾符

指定以下diouxX轉換說明符適用於intmax_tuintmax_t參數; 或者后面的n轉換說明符適用於指向intmax_t參數的指針。

還有size_tptrdiff_t (以及它們對應的有符號/無符號類型)的zt修飾符。

個人而言,我發現<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並為所有值獲得明確定義的結果,但打印-118446744073709551615 (2 64 -1)可能會有點混亂。

所有這些只有在您的C實現支持<stdint.h>printfj長度修飾符時才有效 - 即,如果它支持C99。 Microsoft ). 並非所有編譯器都這樣做( 微軟 )。 對於C90,最寬的整數類型是longunsigned long ,您可以轉換為那些並使用"%ld"和/或"%lu" 理論上,您可以使用__STDC_VERSION__預定義宏來測試C99合規性 - 盡管某些C99之前的編譯器可能仍然支持寬度大於longunsigned long類型作為擴展。

整數類型的“大小”在這里不相關,但是它的值范圍。

顯然你已經嘗試過了,可以轉換為uintmax_tintmax_t來輕松解決printf()調用中的任何歧義。

簽名或無符號類型的問題可以通過簡單的方式解決:

  • 對於某些正值N,所有無符號整數運算都以模“N”為模,具體取決於類型。 它意味着只涉及無符號整數類型的每個結果都給出一個非負值。
  • 要檢測變量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在有符號或無符號類型中具有相同的位表示。

第一個宏可用於檢測算術對象的基礎類型是否具有未編號類型。
此結果在第二個宏中用於選擇在屏幕上打印對象的方式。

第一次罰款:

  • 正如Pascal Cuoq在他的評論中所指出的那樣,整數促銷必須在accont中采用unsigned charshort值在int范圍內進行。 這相當於詢問該值是否在INT_MAX 0到INT_MAX

所以我已經將宏的名稱更改為fits_signed_type
此外,我已修改宏以考慮正int值。

在大多數情況下,macro fits_unsigned_type可以判斷對象是否具有無符號整數類型。

  • 如果值為負,顯然類型不是unsigned
  • 如果值N是正的那么
    • 如果-N為正,則N具​​有unsigned類型,
    • 如果-N為負,但N在0到INT_MAX的范圍內,則N的類型可以是有signedunsigned ,但它適合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"));
}

但是存在一個潛在的問題:如果存在與列出的類型不同的類型(即,除了有signedunsigned版本的charshortintlonglong long ),這對這些類型不起作用。 也不可能將size_tintmax_t等類型添加到類型通用宏“以防萬一”,因為如果它們已經列出的類型之一的typedef ,則會導致錯誤。 我沒有指定default情況,以便在沒有找到匹配類型時宏生成編譯時錯誤。

但是,如示例程序中所示, size_tintmax_t在平台上工作正常,它們與列出的類型之一相同(例如, long相同)。 類似地,如果例如longlong long ,或longint是相同類型,則沒有問題。 但是更安全的版本可能只是根據簽名轉換為intmax_tuintmax_t (如其他答案中所示),並且僅使用那些選項創建類型通用宏...

一個美觀的問題是類型泛型宏在字符串文字周圍用括號擴展,防止以通常的方式連接相鄰的字符串文字。 這可以防止以下情況:

(void) printf("var = " PRI(var) "\n", var); // does not work!

因此, PRIFMT宏包含前綴和后綴,用於打印單個變量的常見情況:

(void) printf(PRIFMT("var = ", var, "\n"));

(請注意,使用printf支持的非整數類型擴展此宏會很簡單,例如doublechar * ...)

暫無
暫無

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

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