ポインタをint型にキャストするな!

高木です。おはようございます。

先日、職場で話題になったことがありますので、せっかくなのでブログで共有することにします。
タイトルの通りで、「ポインタをint型にキャストするな!」という話です。

具体的にダメなコードを挙げることにしましょう。

この例はわかりやすくするための簡略化したものです。

&array[3]というポインタをint型にキャストしていますね。
これがダメなのです。

ポインタ型というのは、処理系によってサイズがまったく異なります。
ちなみに同じ処理系であっても何型のポインタかによってサイズが変わることがあります。
少なくともオブジェクト型や不完全型と関数型ではサイズが異なることは普通です。
ハーバード・アーキテクチャを考えれば容易に想像が付くことでしょう。

仮に同じ処理系のポインタがすべて同じ型であったとしても、それを整数型に型変換したときにint型の表現範囲に収まるとは限りません。
64ビットの処理系のポインタが32ビットのint型の表現範囲に収まるはずがありませんよね。

ポインタ型と整数型の相互変換を行いたいのであれば、intptr_t型またはuintptr_t型を使う必要があります。
事実上、それ以外の選択肢はありません。

unsigned char array[10];
intptr_t value = (intptr_t)&array[3];

厳密なことをいえば、intptr_t型やuintptr_t型との相互変換が可能なのはvoidへのポインタのみです。
unsigned charへのポインタなど、voidへのポインタ以外では上手くいくとは限らないのです。
ただ、これについてはよほどの潔癖症でなければ許容してもよいとは思います。

もうひとつ別の例を挙げることにしましょう

これもやはりダメですね。
&array[5]などのポインタをint型にキャストした時点ですでにデタラメです。
ポインタの差を求めたいのであれば、整数型にキャストせずにポインタどうしを直接減算する必要があります。
そして、結果を格納する変数はptrdiff_t型にしなければなりません。

ところで、intptr_t型やptrdiff_t型をprintf系関数で書式化するにはどうすればいいのでしょうか?
%dを使うのは実引数がint型であることを期待しているのでダメです。
それぞれ専用の書式を使う必要があります。

先ほどまでの例で出てきたintptr_t型のvalue、およびptrdiff_t型のoffsetを書式化する例を挙げます。

ptrdiff_t型の場合は”%td”のようにサイズ指定にtを使えばいいのですが、intptr_t型の場合はちょっと面倒です。
<inttypes.h>ヘッダにPRIdPTRなどのマクロがあるので、これを使う必要があります。
普通はPRIdPTRマクロなんかを使うより、ポインタを直接”%p”で変換するほうが楽ですけどね。