web-dev-qa-db-ja.com

ino_tのような未知のサイズのタイプを印刷する方法は?

実装で定義されたサイズの整数型の値(ino_ttime_tなど)をprintfで出力したい状況をよく経験します。今、私はこれにこのようなパターンを使用しています:

#include <inttypes.h>

ino_t ino; /* variable of unknown size */
printf("%" PRIuMAX, (uintmax_t)ino);

このアプローチはこれまでのところ機能しますが、いくつかの欠点があります。

  • 印刷しようとしているタイプが符号付きか符号なしかを知る必要があります。
  • コードを拡大する可能性のある型キャストを使用する必要があります。

より良い戦略はありますか?

23
fuz
#include <inttypes.h>
ino_t ino; /* variable of unknown size */
/* ... */
printf("%" PRIuMAX, (uintmax_t)ino);

それは確かに機能します(いくつかの条件付き。以下を参照)が、私は使用します:

printf("%ju", (uintmax_t)ino);

j長さ修飾子

次のdioux、または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を使用して、すべての値に対して明確に定義された結果を取得できます(264-1)少し混乱するかもしれません。

これはすべて、C実装が<stdint.h>jprintf長さ修飾子をサポートしている場合、つまりC99をサポートしている場合にのみ機能します。すべてのコンパイラがそうするわけではありません(マイクロソフト)。 C90の場合、最も広い整数型はlongおよびunsigned longであり、これらに変換して"%ld"および/または"%lu"を使用できます。理論的には、__STDC_VERSION__事前定義マクロを使用してC99準拠をテストできますが、一部のC99以前のコンパイラは、拡張機能としてlongおよびunsigned longよりも広い型をサポートしている場合があります。

9
Keith Thompson

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バージョンのcharshortintlong、およびlong long以外)、これはそれらのタイプでは機能しません。 size_tintmax_tなどの型を「万が一に備えて」型ジェネリックマクロに追加することもできません。これらがaretypedefd fromすでにリストされているタイプの1つ。 defaultケースを指定しないままにして、一致するタイプが見つからない場合にマクロがコンパイル時エラーを生成するようにしました。

ただし、サンプルプログラムに示されているように、size_tintmax_tは、リストされているタイプの1つと同じ(たとえば、longと同じ)プラットフォームでは問題なく機能します。同様に、たとえば、longlong long、またはlongintが同じタイプの場合、問題はありません。しかし、より安全なバージョンは、(他の回答に見られるように)符号に応じてintmax_tまたはuintmax_tにキャストし、これらのオプションのみを使用して型ジェネリックマクロを作成することです…

外観上の問題は、ジェネリック型マクロが文字列リテラルを括弧で囲んで展開し、通常の方法で隣接する文字列リテラルとの連結を妨げることです。これにより、次のようなことが防止されます。

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

したがって、単一の変数を出力する一般的なケースに含まれる接頭辞と接尾辞を持つPRIFMTマクロ:

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

printfでサポートされている非整数型でこのマクロを展開するのは簡単であることに注意してください。例:doublechar *…)

7
Arkku

整数型の「サイズ」はここでは関係ありませんが、その値の範囲は関係があります。

どうやらあなたが試したように、まだ、_uintmax_t_と_intmax_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は符号付きまたは符号なしで同じビット表現を持つため、すべてが正常に機能します。タイプ。

最初のマクロは、算術オブジェクトの基になる型にsgining型がないかどうかを検出するために使用できます。
この結果は、オブジェクトが画面に印刷される方法を選択するために2番目のマクロで使用されます。

第1版:

  • Pascal Cuoqが彼のコメントで指摘しているように、整数の昇格は、charの範囲内にある符号なしのshortおよびint値に対応する必要があります。これは、値がランゴ0から_INT_MAX_にあるかどうかを尋ねるのと同じです。

そこで、マクロの名前を_fits_signed_type_に変更しました。
また、正のint値を考慮に入れるようにマクロを変更しました。

マクロ_fits_unsigned_type_は、ほとんどの場合、オブジェクトが符号なし整数型であるかどうかを判別できます。

  • 値が負の場合、明らかにタイプはunsignedではありません。
  • 値Nが正の場合、
    • _-N_が正の場合、Nはunsignedタイプであり、
    • _-N_が負で、Nが0から_INT_MAX_の範囲にある場合、Nのタイプはsignedまたはunsignedになりますが、intの正の値の範囲に収まります。 _uintmax_t_の範囲。

第2版:

Irは、同じ問題を解決するためのアプローチがここにあるようです。私のアプローチでは、値の範囲と整数の昇格規則を考慮して、printf()で正しい出力値を生成します。一方、Grzegorz Szpetkowskiのアプローチは、タイプの符号付き文字をストレート形式で決定します。私は両方好き。

6
pablo1977

すでにC99ヘッダーを使用しているため、sizeof(T)および符号付き/符号なしチェックに応じて、正確な幅のフォーマット指定子を使用する可能性があります。ただし、これは前処理フェーズの後に実行する必要があります(悲しいことに##演算子をここで使用して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 のちょっとしたトリックが必要ですが、他の方法もあるかもしれません(これは一般的な「価格」です)。

編集:

これが2番目のバージョンで、関数のようなマクロを使用して特定のコンパイラ拡張を必要としません(私もそれを読むことができないので心配しないでください)。

#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;
}

条件演算子は左の連想性を持っていることに注意してください(したがって、意図したとおりに左から右に評価されます)。