http://www.faireal.net/ より転載。体裁の変更はtitoによる。 64ビットねた3題 †2007年3月3日 BOOLとbool †Windows のAPIではBOOLは32ビットでも64ビットでもINT32の別名に過ぎないため、こういうおかしなことが起きてくる。 UINT64 size = 0x100000000ui64; ... BOOL bNonZeroSize = (BOOL) size; // FALSE bool fNonZeroSize = (bool) size; // true sizeがゼロでないときフラグを立てようとしているのだが、 (BOOL)は破綻し、(bool)はうまく行く。 64ビットのデータを渡してゼロではないが、たまたま下位32ビットが0の場合だ。 boolは1バイトしかないが、ホントのブール演算なので非ゼロの物体を変換すればtrueになる。 BOOLは4バイトも取っているものの、ニセモノの真偽値(実態はただのINT32)なのだ。 このフラグを立てるしくみがマクロ関数など分かりにくい場所にあると、 2^32=43億回に1回の割合でしか間違わないので、相当気づきにくいだろう。 変換元がSIZE_Tまたはsize_tだと64ビット環境でのみ64ビットになるのでさらに微妙になる。 VC6では、上記のように書くと、意図通り動作するboolへのキャストについて warning C4800の警告が出るが、誤動作の原因であるBOOLへのキャストについては警告されない。 コンパイラからは、(INT32)(UINT64)と明示的にキャストしているように見えるからだ。 (bool) も明示的にキャストしているのだから、警告する必要ない気がするが…ともかくこの場合、 正しい方は警告され、誤動作する方は何も言われない。 よくある方法は、否定演算子を2個書いて、明示的にtrue/falseに二値化することだろう。 BOOL bNonZeroSize = !! size; // TRUE bool fNonZeroSize = !! size; // true !の戻り値はboolなので、BOOLの場合、1バイトのtrue/falseを再び4バイトの0/1に戻していることになる。 もしくは、さらに明示的に、 BOOL bNonZeroSize = ( size != 0 )? TRUE : FALSE ; bool fNonZeroSize = ( size != 0 ); 戻り値がTRUEかFALSEになっているとき、 戻り値の型をBOOLでなくLRESULTなどにしてしまうことも考えられる。 32ビット環境では実質的に同じことだが、64ビットのときLRESULTはINT64に拡大してくれるのに対して、 BOOLはINT32…。 例えば何かが成功するとポインターがNULLでなくなって、その成功したという情報が戻り値になる関数があるとして、 ポインターそのものを戻り値にした場合(良いスタイルではないが)、 32ビット環境で戻り値がBOOLのときポインターがNULLなら戻り値0、非NULLなら戻り値非0、 と計画通りに行くのに対して(VCではNULLは0)、 64ビット環境では、ポインターがヌルではないのだが下位32ビットがたまたま全部ゼロだと(43億回に1回は起こりうる)BOOLの場合、 成功しているのに失敗したという答えが返ってしまう。LRESULTだと上位32ビットも返してくれるので、うまく行く。 そんな書き方自体いかん!と思うでしょうが、 マイクロソフト純正ドキュメントにこれをさらにひどくしたものがあるのです。 すなわち… WM_CTLCOLOR*の仕様書を守ると不幸が… †MSDNのドキュメントで、ブラシなどのハンドルを(BOOL)に変換して戻せというのがあるが、 それに従うと、破綻する。例えば… If an application processes this message, the return value is a handle to a brush... If a dialog box procedure handles this message, it should cast the desired return value to a BOOL and return the value directly. WM_CTLCOLORSTATIC Notificationより(2007年3月現在) ハンドルなので、(BOOL)ではなく(LRESULT)に変換しないと64ビットではまずいのでは? MSDNの説明を守って HandleToLong などをかませて(BOOL)にキャストすると、上位32ビットがたまたま全部ゼロでない限り、 変なことになる…と思う。(実際には64ビットのテスト環境がないので分からないけど…) /Wp64でのGet/SetWindowLongPtrに対するアホ警告を黙らせるには… †わざわざ〜Longでなく〜LongPtrを使ってあげてるのに、どこも間違ってないのに、アホな警告が…。 要するにコンパイラーがいまいちなのだ。 32ビット環境でLONGとLONG_PTRは同じなのに、64ビット環境に対するチェックとして、それを区別しようとするから、 変なことになる。 別に64に対応しようという気はなくても、警告が出るのはうざいし、 紛らわしい。ので、次のような_Floralを定義し、 Get/SetWindowLongPtrが必要なときはいつでも_Floralと書いておくとつじつまが合う。 コンパイラが改善したら_Floral版=単なるオリジナルの別名に再定義すれば簡単に戻せる。 この_Floral版は64ビットでは何もせず、 32ビットでは単にLONG_PTRとLONGの間を見かけ上型変換してるだけで、それは要するにINT32同士で何もしないということであるが、 この何もしない処理でコンパイラのご機嫌は良くなるのである。 コンパイラの不具合に腹を立ててぐだぐだ言っても問題は解決しない。 限界があるものはあるのだから、それに合わせて共存することが必要だろう。 #ifdef _WIN64 #define SetWindowLongPtr_Floral SetWindowLongPtr #define GetWindowLongPtr_Floral GetWindowLongPtr #else inline LONG_PTR SetWindowLongPtr_Floral( HWND hWnd, int nIndex, LONG_PTR NewLongPtr ) { return SetWindowLongPtr( hWnd, nIndex, (LONG) NewLongPtr ); } inline LONG_PTR GetWindowLongPtr_Floral( HWND hWnd, int nIndex ) { return (LONG_PTR) GetWindowLongPtr( hWnd, nIndex ); } #endif |