32位系統上使用64位變量需要注意

32位系統,eax,ecx,edx,ebx這些寄存器都是32位的,而要使用一個64位的變量,需要用到2個寄存器,或者一個寄存器用到2次,往往在某些地方就會出現意想不到的問題。

今天參加了CSDN的英雄會,有幸見了些名人,回到家上CSDN,看到個帖子
http://topic.csdn.net/u/20080905/16/3823c75d-c33b-4ea0-83b1-8386d03e6c6c.html
具體內容:

題目:
1、不能用庫函數,要求達到效率o(1);
2、將符號'@'插入字符串ptr的首位,字符串ptr原內容按照原來的順序排在'@'之後.

None.gifvoid insert(char *str, char tmp)
ExpandedBlockStart.gif
{
InBlock.gif     
//填寫代碼:
ExpandedBlockEnd.gif
}

None.gif
void main(void)
ExpandedBlockStart.gif
{
InBlock.gif    
char ptr[16]="abcdefg";
InBlock.gif    
char temp='@';
InBlock.gif    insert(ptr, temp);
InBlock.gif    printf(
"%s/n;",ptr);
ExpandedBlockEnd.gif}

None.gif

我很容易想到
None.gifvoid insert(char *str, char tmp)
ExpandedBlockStart.gif
{
InBlock.gif    
*((__int64*)(str + 1)) = *(__int64*)str;
InBlock.gif    
*str = tmp;
ExpandedBlockEnd.gif}

可是結果卻很令人驚訝,輸出@abcddfg,有一個字節不對。仔細一想,應該是把64位變量放到2個寄存器中了。
用OD反一下,看下主函數裏的關鍵地方,OH,前面分配棧的一句是sub esp,18

 1None.gif00401030  /$  A1 DCB64000   mov     eax, dword ptr [40B6DC]  
 2None.gif00401035  |?  8945 EC       mov     dword ptr [ebp-14], eax
 3None.gif00401038  |?  8B0D E0B64000 mov     ecx, dword ptr [40B6E0]
 4None.gif0040103E  |?  894D F0       mov     dword ptr [ebp-10], ecx
 5None.gif00401041  |?  33D2          xor     edx, edx                         ;  namespac.0040E2B8
 6None.gif00401043  |?  8955 F4       mov     dword ptr [ebp-C], edx
 7None.gif00401046  |?  8955 F8       mov     dword ptr [ebp-8], edx
 8None.gif00401049  |?  C645 EB 40    mov     byte ptr [ebp-15], 40
 9None.gif0040104D  |?  0FB645 EB     movzx   eax, byte ptr [ebp-15]
10None.gif00401051  |.  50            push    eax
11None.gif00401052  |?  8D4D EC       lea     ecx, dword ptr [ebp-14]
12None.gif00401055  |?  51            push    ecx
13None.gif00401056  |.  E8 A5FFFFFF   call    00401000
14None.gif0040105B  |?  83C4 08       add     esp, 8
第一行,0x04B6DC就是常量字符串"abcdefg"的地址,把分2次每次4個送入棧,完成char ptr[16]的初始化,第8 9行是把'@'放入eax,第10行把最後一個參數入棧,也就是@,11行把ebp-14也就是ptr傳給ecx,12行把ptr入棧,也就是倒數第二個參數,然後調用下面的函數。

 1None.gif00401000  /$  55            push    ebp
 2None.gif00401001  |.  8BEC          mov     ebp, esp
 3None.gif00401003  |.  8B45 08       mov     eax, dword ptr [ebp+8]
 4None.gif00401006  |.  8B4D 08       mov     ecx, dword ptr [ebp+8]
 5None.gif00401009  |?  8B11          mov     edx, dword ptr [ecx]
 6None.gif0040100B  |.  8950 01       mov     dword ptr [eax+1], edx
 7None.gif0040100E  |?  8B49 04       mov     ecx, dword ptr [ecx+4]
 8None.gif00401011  |?  8948 05       mov     dword ptr [eax+5], ecx
 9None.gif00401014  |?  8B55 08       mov     edx, dword ptr [ebp+8]
10None.gif00401017  |.  8A45 0C       mov     al, byte ptr [ebp+C]
11None.gif0040101A  |.  8802          mov     byte ptr [edx], al
12None.gif0040101C  |?  5D            pop     ebp
13None.gif0040101D  |.  C3            retn
3 4行把剛纔入棧的ptr指針存入eax,ecx
第5行把char ptr[16]的前4個字節abcd存入edx,也就是0x64636261,注意高低位
然後把edx裏的4個字節的數,寫入ptr+1的位置,可見問題就出現在這裏,一下寫入4個字節,在ptr+1到ptr+4的位置,由於*(ptr+4)裏的內容並未保存,所以被覆蓋了,導致後面第2次讀取的數據不正確,最後的結果也不會輸出正確

看了下邊網友的回帖,比較好的方法就是用移位,本來是數,移位肯定不會出問題,使用的是shld雙精度左移指令(爲什麼是左移不是右移?同樣注意高低位),保證數據不會丟失
None.gifvoid insert(char *str, char tmp)
ExpandedBlockStart.gif
{
InBlock.gif    
*(__int64*)str <<= 8;
InBlock.gif    
*str = tmp;
ExpandedBlockEnd.gif}
運行,結果正確

可以看出,在32位系統使用64位變量需要很注意,尤其是在賦值的時候,比如我上邊的例子。往往在一個大工程裏,出現這樣的問題,很難查出原因來,因此,需要格外注意。還有在多線程的時候,一個讀一個寫,由於使用2個寄存器,就有可能在一個寫線程操作到一個64位數的32位的時候,線程正好切換到讀線程,導致產生一些奇怪的數據,而且這種奇怪的情況並不是每次運行都能體現出來,造成的損失可想而知。所以對跨線程使用64位變量必須嚴格進行同步。


by greatws
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章