さて、今更何でこの話題なのかといいますと、さすがに見つかったゆがみの量がとんでもないレベルになりつつあるからですが・・・。
といっても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[1]では32bit、LP64[2]では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をルーターから通過させないような設定になっているんですね・・・。勉強になりました・・・。
- 64bitプログラムでlong longおよびポインタが64bit値となるコンパイラ。intやlongは32bit値となる。VC++はこれ[back]
- 64bitプログラムでlong、long longおよびポインタが64bit値となるコンパイラ。intが32bit値となる。GCCはこれ[back]