以前、データ型モデルの話を書いた。
64bitのシステムではポインタ型が64bitになるのはともかくとして、
それに伴ってlong型も64bitになるLP64というモデルがある。
UNIX系のシステムのコーディングではポインタを格納する整数にlong型を使う慣例があり、
その場合は修正量が少ないのだが、そういう使い方でなければ面倒なわけである。
元々、32bitのシステムのデータ型モデルであるILP32では、int型もlong型も同じ型で、
ゆえに使い分けもなく漫然とintとlongを混在させていたのが実情である。
移植にあたってはとりあえずはint型に統一してから考えようということになった。
で、新年早々に「全部、longからintに置き換えたコード作りましたよ」
というのでソースコードを見るとコメントのlongまでintに置換されていたので、
一括置換してから、ビルドエラーになる部分を調整してやったんだろう。
ただ、ワーニングはいろいろあるわけで……
int* ptr1, ptr2; ptr2 = (int*)((unsigned int)ptr1 + (unsigned int)size2);
ポインタ型をint型にキャストして足し算して、ポインタ型に戻しているが、
64bit→32bit→64bitという変換になってしまっている。
元々は全て32bitだったから特に問題はなかった。
元がintだったかlongだったかはわからないけど、特に規則性はないので。
というわけでこの計算は64bit幅のlong型でやるべきなのだけど、
単純にlongに書き換えると修正漏れなのかパッと見てわからない。
で、こういう場合に使える表現があって、それが uintptr_t型である。
stdint.hで定義されている型の1つで、ポインタ型と同じ幅を持った符号無し整数型である。
これを使えばさっきのはこう書ける。
ptr2 = (int*)((uintptr_t)ptr1 + (uintptr_t)size2);
これだと意図が明確ですよね。
ところで他にもポインタ型と同じ整数型を表してそうなものがある。
stddef.hで定義されている size_t と ptrdiff_t である。
size_tはsizeof演算子の結果が格納できる符号無し整数型ということになっている。
現実的に配列などのサイズがそんなに巨大であることは考えにくいけど、
アドレス空間が64bitのシステムならば char arr[0x4000000000000]; みたいな配列を表現することはできる。
1PB(ペタバイト)のメモリを確保できるシステムが存在するかはさておき。
なので size_t型はポインタ型と同じ幅の符号無し整数型、すなわちuintptr_t であるのが通常である。
この配列の先頭と末尾の添字の差は -0x3FFFFFFFFFFFF~0x3FFFFFFFFFFFF の範囲となるが、
そのような数値を格納できる型というのはポインタ型と同じ幅の符号付き整数型、すなわち intptr_t であるのが通常だという。
size_t と uintptr_t が同じ、ptrdiff_t と intptr_t が同じである保証はない。
違う型にする理由がないので実態としては同じ型であると考えてよいのだが。
配列の添字を表現する型としては size_t が最も適しているから、
for(size_t i=0; i<len; i++){
sum += arr[i]; }
というようなときに使うとよいけど、そういう書き方が一般的とは思わない。
さっきのポインタ演算だけど、char型のポインタを使ってこう書いても同じ意味になる。
ptr2 = (int*)((char*)ptr1 + (size_t)size2);
ポインタ型は1進めると、配列要素1個分進むことになっている。
なのでint型のポインタにy加算すると、ポインタの数値は4×y増えることになる。
これは面倒な話なのだが、1byte型であるchar型のポインタだと加算した数値がそのまま進むことになる。
なので一旦char*型にキャストして加算して、目的のポインタ型にキャストするという方法が考えられる。
さっき書いたように配列の添字を表す型としてはsize_t型が最も適しているので、
size2はsize_t型にキャストしてchar*型に加算している。
ただ、この書き方もそれはそれでわかりにくいなと思ったので、
冒頭に書いたように uintptr_t型を使うのが最もわかりやすいと思った。
結局のところはポインタ型のサイズの整数型で演算が行われるわけですからね。
というわけで、多分こう言う形になるんじゃないか。