VC++の__int64の罠にハマった\(^o^)/
VC++で4GB(unsigned 32bit境界)を超えるファイルサイズを扱うアプリケーションを作っていて
VC++のコンパイラの癖の罠にハマった\(^o^)/のでメモ。
問題:次のようなコードはどういう結果になりますか?(・ω・`)
#include "stdafx.h" void _testfunc(unsigned __int64 num) { printf("%I64d (%I64x)\r\n", num, num); } int _tmain(int argc, _TCHAR* argv[]) { unsigned int a = 0xffffffff; unsigned int b = 0x10000000; unsigned __int64 c = a + b; unsigned __int64 d = (unsigned __int64)a + (unsigned __int64)b; _testfunc( a + b); _testfunc( c ); _testfunc( d ); return 0; }
これを実行するとこうなる。
つまり正しい結果が出るのは_testfunc( d );の
unsigned __int64 d = (unsigned __int64)a + (unsigned __int64)b;
という処理をしたときのみ。
よく考えれば当たり前なんですが、
unsigned int a = 0xffffffff; unsigned int b = 0x10000000; unsigned __int64 c = a + b;
という計算は間違いで、a+bという計算はたとえ代入先が__int64 cと定義されていても一時的にint(32bit)として加算され32bitを超えるビットが落ちてしまうのでござる。
これはまあ分かるにしても、今回ハマったのは、
void _testfunc(unsigned __int64 num);
と定義してある、つまり引数に64bit値をとる関数プロトタイプ宣言をしてあっても、うっかり
_testfunc( a + b);
と書いてしまったらうまくいかないんだな(´・ω・`)
つまり
_testfunc( (__int64)a + (__int64)b);
として渡してやらないと正しい値が渡らない。
ねーよwwwwww
と思う方は例えばこんなコードを想定。
_testfunc( blocksize * num_blocks );
この場合、blocksizeもnum_blocksも__int64で宣言されていれば問題ないのですが、構造上blocksizeは最大32KBだからunsigned long blocksize; でいいや、と思っていると罠にハマる。
void _testfunc(unsigned __int64 num) { 004113B0 push ebp 004113B1 mov ebp,esp 004113B3 sub esp,0C0h 004113B9 push ebx 004113BA push esi 004113BB push edi 004113BC lea edi,[ebp-0C0h] 004113C2 mov ecx,30h 004113C7 mov eax,0CCCCCCCCh 004113CC rep stos dword ptr es:[edi] printf("%I64d (%I64x)\r\n", num, num); (途中省略) 00411408 ret int _tmain(int argc, _TCHAR* argv[]) { 00411420 push ebp 00411421 mov ebp,esp 00411423 sub esp,0F8h 00411429 push ebx 0041142A push esi 0041142B push edi 0041142C lea edi,[ebp-0F8h] 00411432 mov ecx,3Eh 00411437 mov eax,0CCCCCCCCh 0041143C rep stos dword ptr es:[edi] unsigned int a = 0xffffffff; 0041143E mov dword ptr [a],0FFFFFFFFh unsigned int b = 0x10000000; 00411445 mov dword ptr [b],10000000h unsigned __int64 c = a + b; 0041144C mov eax,dword ptr [a] 0041144F add eax,dword ptr [b] # eax,ecxは32bit汎用レジスタ 00411452 xor ecx,ecx # 上位32ビットをクリア 00411454 mov dword ptr [c],eax 00411457 mov dword ptr [ebp-20h],ecx unsigned __int64 d = (unsigned __int64)a + (unsigned __int64)b; 0041145A mov eax,dword ptr [a] 0041145D xor ecx,ecx 0041145F mov edx,dword ptr [b] 00411462 xor esi,esi 00411464 add eax,edx 00411466 adc ecx,esi # 上位32ビットキャリーをちゃんと足している 00411468 mov dword ptr [d],eax 0041146B mov dword ptr [ebp-30h],ecx _testfunc( a + b); 0041146E mov eax,dword ptr [a] 00411471 add eax,dword ptr [b] 00411474 xor ecx,ecx # 上位32ビットをクリア 00411476 push ecx 00411477 push eax 00411478 call _testfunc (4111D6h) 0041147D add esp,8 _testfunc( c ); 00411480 mov eax,dword ptr [ebp-20h] 00411483 push eax 00411484 mov ecx,dword ptr [c] 00411487 push ecx 00411488 call _testfunc (4111D6h) 0041148D add esp,8 _testfunc( d ); 00411490 mov eax,dword ptr [ebp-30h] 00411493 push eax 00411494 mov ecx,dword ptr [d] 00411497 push ecx 00411498 call _testfunc (4111D6h) 0041149D add esp,8 return 0; (以下略)