読者です 読者をやめる 読者になる 読者になる

syghの新フラグメント置き場

プログラミングTipsやコード断片の保管場所です。お絵描きもときどき載せます。

size_t, ptrdiff_tのprintf書式指定

C++ プログラミングTips

(これは2010-12-05に書いた故OCNブログの記事を移植したものです)

size_tとptrdiff_tはポインタ同様、32bit向け(_M_IX86とか)と64bit向け(_M_X64とか)でサイズと上下限が変わるプラットフォーム依存の整数型なので、printf()系の関数で出力する場合、書式指定(長さ修飾子、length modifier)に注意しないといけないのですが、MSVC(Visual C++)のCRTでは拡張書式として%Id, %Iuなどが用意されています。フォントによっては分かりづらいかもしれませんが、小文字のエルではなく大文字のアイです。

詳しくはMSDNライブラリの「サイズ指定」を見てね、ってことなんですが、具体的な書式指定の仕方を挙げておきます。

printf("1L  << 16 by %%I32d = %I32d\n", 1L << 16);
printf("1LL << 48 by %%I64d = %I64d\n", 1LL << 48);
printf("INT32_MAX by %%I32d = %I32d\n", int32_t(INT32_MAX));
printf("INT64_MAX by %%I64d = %I64d\n", int64_t(INT64_MAX));
printf("-1 by %%I32u = %I32u\n", uint32_t(-1)); // 大抵の処理系で、UINT32_MAX と同義のはず。
printf("-1 by %%I64u = %I64u\n", uint64_t(-1)); // 大抵の処理系で、UINT64_MAX と同義のはず。
printf("PTRDIFF_MAX by %%Id = %Id\n", ptrdiff_t(PTRDIFF_MAX)); // ターゲット プラットフォーム依存。
printf("SIZE_MAX    by %%Iu = %Iu\n", size_t(SIZE_MAX)); // ターゲット プラットフォーム依存。

// %Id, %Iu はそれぞれ ptrdiff_t, size_t 相当の型(ポインタ互換整数型)に対して使う。
// %ld, %lu はそれぞれ signed long, unsigned long 用。Win32/Win64 では int と long のサイズが同じなので %d, %u と違いはないが、Win16 では区別される。
// signed long long および unsigned long long にはそれぞれ %lld, %llu を使う。Win32/Win64 では 64bit 整数になる。

int32_tやint64_tなどはC99やC++11 (C++0x TR1) の <stdint.h>, <cstdint> で定義されている、処理系ごとにサイズ保証された型なんですが、VC++では2010以降で実装されています。

ちなみにWindows APIでよく見かけるINT_PTR、UINT_PTR、LONG_PTR、ULONG_PTR、DWORD_PTRなども、単に「ポインタと相互変換してもいい整数型(アドレスを格納できるだけの幅を持った整数型)」という意味のプラットフォーム依存型なので注意しましょう。.NETでいうとSystem.IntPtrSystem.UIntPtrに相当します。

まぁ16進数表記でかまわないんであれば、size_tもptrdiff_tも従来の標準規格に沿った%p書式指定を使って出力すればよいです。

あと、こういう環境依存の型に対する操作は、C++のストリームのほうが本質的に型安全で、楽といえば楽です。C/C++で可変個引数を使う場合、うかつに変数の型を変えられないというデメリットがあります(必ず対応する書式も注意深く修正する必要があります*1)。

なお、gccでは%Id, %Iuの代わりに%zd, %zu, %td, %tuなどの書式が使えるようです。これらはC99規格の書式らしいです。

MSVCがなぜC99をサポートしないのか、なぜ独自拡張にこだわるのか、詳しいことはよくわからないんですが、64bit版WindowsLinux系とは違ってLLP64モデルであること、Windowsネイティブ開発における事実上標準言語がC++であることに起因しているような気がします。C言語オープンソース界隈とかは、MSVCがC言語のサポート強化を怠けてる状況に憤りをおぼえているかもしれません(クロスプラットフォームなライブラリを作る際は一番遅れてる処理系に合わせた規格でコードを書かないといけないので、あるOS上での事実上標準の処理系が他に遅れをとってると非常に困る)。
しかし、C言語によるコード生産性はすでに他の言語と比べると大きく遅れをとっており、せいぜいアセンブラよりはマシといった程度でしかないので、WindowsプログラマーにとってC++(もしくはC#)の修得は必須ですが、Cはほとんど利用する場面がありません(ドライバー開発は除く)。それゆえ、MSにとってCはもはや死んだ言語同然なのだと思われます。ちなみに個人的には

C#の文法>>C++の文法>>>>>>>>>>Cの文法」

くらいの差があると考えています。文法仕様の品質・完成度は言語機能同様、コードの生産性やメンテナンス性に大きく影響を及ぼします。

*1:学生の頃、16bit環境向けプログラムの変数の型を一部intからlongに変えてサイズ拡張する作業をやったことがありましたが、そのときに対応する書式をきちんと変えなかったため、データが正常に出力されなくなるバグを混入させてしまったことがあります。