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;
(以下略)