実装で定義されたサイズの整数型の値(ino_t
やtime_t
など)をprintf
で出力したい状況をよく経験します。今、私はこれにこのようなパターンを使用しています:
#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
を使用して、すべての値に対して明確に定義された結果を取得できます(264-1)少し混乱するかもしれません。
これはすべて、C実装が<stdint.h>
とj
のprintf
長さ修飾子をサポートしている場合、つまりC99をサポートしている場合にのみ機能します。すべてのコンパイラがそうするわけではありません(咳マイクロソフト咳)。 C90の場合、最も広い整数型はlong
およびunsigned long
であり、これらに変換して"%ld"
および/または"%lu"
を使用できます。理論的には、__STDC_VERSION__
事前定義マクロを使用してC99準拠をテストできますが、一部のC99以前のコンパイラは、拡張機能としてlong
およびunsigned long
よりも広い型をサポートしている場合があります。
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
などの型を「万が一に備えて」型ジェネリックマクロに追加することもできません。これらがaretypedef
d fromすでにリストされているタイプの1つ。 default
ケースを指定しないままにして、一致するタイプが見つからない場合にマクロがコンパイル時エラーを生成するようにしました。
ただし、サンプルプログラムに示されているように、size_t
とintmax_t
は、リストされているタイプの1つと同じ(たとえば、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 *
…)
整数型の「サイズ」はここでは関係ありませんが、その値の範囲は関係があります。
どうやらあなたが試したように、まだ、_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は符号付きまたは符号なしで同じビット表現を持つため、すべてが正常に機能します。タイプ。
最初のマクロは、算術オブジェクトの基になる型にsgining型がないかどうかを検出するために使用できます。
この結果は、オブジェクトが画面に印刷される方法を選択するために2番目のマクロで使用されます。
第1版:
char
の範囲内にある符号なしのshort
およびint
値に対応する必要があります。これは、値がランゴ0から_INT_MAX
_にあるかどうかを尋ねるのと同じです。そこで、マクロの名前を_fits_signed_type
_に変更しました。
また、正のint
値を考慮に入れるようにマクロを変更しました。
マクロ_fits_unsigned_type
_は、ほとんどの場合、オブジェクトが符号なし整数型であるかどうかを判別できます。
unsigned
ではありません。-N
_が正の場合、Nはunsigned
タイプであり、-N
_が負で、Nが0から_INT_MAX
_の範囲にある場合、N
のタイプはsigned
またはunsigned
になりますが、int
の正の値の範囲に収まります。 _uintmax_t
_の範囲。第2版:
Irは、同じ問題を解決するためのアプローチがここにあるようです。私のアプローチでは、値の範囲と整数の昇格規則を考慮して、printf()
で正しい出力値を生成します。一方、Grzegorz Szpetkowskiのアプローチは、タイプの符号付き文字をストレート形式で決定します。私は両方好き。
すでに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;
}
条件演算子は左の連想性を持っていることに注意してください(したがって、意図したとおりに左から右に評価されます)。