64bitコードへの移植心構え

さて、今更何でこの話題なのかといいますと、さすがに見つかったゆがみの量がとんでもないレベルになりつつあるからですが・・・。

といっても64bitでコード生成したときに実行できない、というものではなく単純に「警告が出るからちょっといやだな~」

「ほかのところで整数型のサイズを合わせ始めるとほかのところで見つかるサイズの異なるポイントでいろいろと怒りたくなる」

というものだったりします。

ポインタを直接扱う

まずは、ポインタですが、これはポインタ型をそのままキャストし続ける限り(int *=>void *など)は問題は絶対に出ません。

これは当たり前ですね。そりゃそれぞれの変数型のサイズはそのコードに依存するようになっているはずですからね。

WinAPI系のHANDLE系の変数(HMODULE,HINSTANCE,HWND,HDC,…)もポインタの一種と考えていいですのでこの定義に当てはまります。

ポインタを整数型にキャストする

整数型にキャストするときはポインタと同じビット長となる変数型を使用する必要があります。これに関しては予約語のレベルでは用意されていないのでライブラリ側から貸してもらう必要があります。

通常のCライブラリで用意されている型は次の通りです。これはlinuxなどでも標準でありますので互換性を重視するときはこちらを使用します。

型名 変数型の状態 備考
intptr_t ポインタ幅の符号付き整数型 int型の拡張
uintptr_t ポインタ幅の符号なし整数型 unsigned int型の拡張

使うには適当なCライブラリのヘッダーファイル(stdlib.hやstddef.hなど)をインクルードすれば使用できます。

WinAPI系であればこの形式がよく出てきますね。

型名 変数型の状態 備考
INT_PTR ポインタ幅の符号付き整数型 intptr_tと同じ意味
UINT_PTR ポインタ幅の符号なし整数型 uintptr_tと同じ意味
LONG_PTR ポインタ幅の符号付き整数型 long型の拡張
DWORD_PTR ポインタ幅の符号なし整数型 DWORD型の拡張
WPARAM ウィンドウプロシージャ系の変数型 ワード値[整数値]を主に受け付ける
LPARAM ウィンドウプロシージャ系の変数型 ロング値[ポインタ系値]を主に受け付ける

この変数型を使用する限りはキャストを行ってもポインタを正常に受け付けれれます。

ちなみに、古いMSDNなんかでint型を使用しているのに新しいMSDNではINT_PTR型になっている関数とかがありますので気をつけましょう~~~

ポインタのサイズを扱う

メモリを確保するときのサイズを指定したり、ポインタ同士の差をとってメモリサイズを取得するときなどに使うコードの動作です。

ポインタのサイズを扱う変数型はライブラリによって微妙に扱い方が違いますので気をつけましょう。

通常のCライブラリでは次の型を使うことが多いです。

型名 変数型の状態 備考
size_t ポインタのサイズを扱う符号なし整数型 sizeof演算子の結果やmallocなどが使用する
ptrdiff_t ポインタのサイズを扱う符号付き整数型 ポインタ同士の減算の結果やiteratorの差の結果などが使用する(なお、任意の型の配列変数hoge(void以外)で&hoge[n] – &hoge[0]はptrdiff_t型のnという値になる)
long 予約された整数型(LLP64(=(64bitプログラムでlong longおよびポインタが64bit値となるコンパイラ。intやlongは32bit値となる。VC++はこれ)=)では32bit、LP64(=(64bitプログラムでlong、long longおよびポインタが64bit値となるコンパイラ。intが32bit値となる。GCCはこれ)=)では64bit) ftellやfseekなどが使用する

最後のものはファイル系の処理(fseekやftellなど)でちょっと処理が違うのでWindows(VC++)からLinux(GCCなど)への移植時には気をつける必要があります。

これがWinAPI系になるとよくわからない状態になります。

型名 変数型の状態 備考
SIZE_T WinAPI系でのsize_t HeapAllocやVirtualAllocなどが使用する
DWORD 32bit符号なし整数型 ReadFileなどが使用する
LONG 32bit符号付き整数型 SetFilePointerなどが使用する
int 予約された整数型。通常は32bit符号付き整数型 lstrlenなどが使用する
LARGE_INTEGER 64bit符号付き変数を扱う構造体 SetFilePointerExなどが使用する
ULARGE_INTEGER 64bit符号なし変数を扱う構造体 IStream::SeekやSHGetDiskFreeSpaceExなどが使用する
LONG_PTR LONGとしてサイズを扱っていたが、64bitコード時にビット幅が足りなくなって付け加えられた変数型 SetWindowLongPtrなどが使用する
DWORD_PTR DWORDとしてサイズを扱っていたが、64bitコード時にビット幅が足りなくなって付け加えられた変数型 waveOutOpenなどが使用する

・・・どれかに統一してくれません?といいたくなりますがこれが事実ですね。

たまにDWORDx2で64bitを無理矢理扱ったりしているやつもいますが。(GetFileSizeなど。これはULARGE_INTEGERを使用してカバーできる)

さすがに常時64bit変数を使うと32bit環境で速度が大幅に落ちるので通常はsize_tをデフォルトにして必要に応じて各変数型へのキャストで対応する、がいいと思います。

構造体内部での変数型

メモリ上だけで扱われる構造体なら変数のサイズなんてどうなったっていいです。同じコンパイラで同じ設定を使う限りは全部吸収してくれます。

問題となるのはファイルに書き出したデータを読み出すときです。処理系依存となる変数型を使用していると構造体のサイズを変えてしまう可能性もありますし、

構造体のアライメント問題で変数の位置がずれたりすることもあります。この辺は

  • コンパイラに依存しないような変数型を使う
  • コンパイラに依存しないような変数型を自分で定義してそれを使う(int32_tなど)
  • 構造体のアライメントを無効化するようにコンパイラに通知する(#pragma pack(push,1) ~ #pragma pack(pop)など)

をそれぞれで使いやすいものを選んで使うといいと思います。

32bitコード(特にWinAPI系)からの移植でのちょっとしたポイント

で、ここで問題になるのは32bitでプログラムを作っているとき、大半の人はDWORDをサイズ型として扱っているのではないでしょうか?

特にファイル処理やモジュール処理ではDWORDが頻繁に登場するので統一するならこれを使う、というわかりやすい選択だと思います。

ここをうまいこと処理することでそれほど問題なく移植ができると思います。必要だと思った場所をsize_tに変更してそれ以外はDWORDで放置しておくとうまいこといけると思います。

ちょっと書き換え作業でイラっと来たので気分転換のための記事でした。

どうでもいいですが、CTUって標準ではPPPoEをルーターから通過させないような設定になっているんですね・・・。勉強になりました・・・。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

この記事のトラックバック用URL