查看單個文章
舊 2007-12-11, 03:19 PM   #2 (permalink)
mini
管理版主
 
mini 的頭像
榮譽勳章
UID - 4144
在線等級: 級別:97 | 在線時長:9851小時 | 升級還需:145小時級別:97 | 在線時長:9851小時 | 升級還需:145小時級別:97 | 在線時長:9851小時 | 升級還需:145小時級別:97 | 在線時長:9851小時 | 升級還需:145小時級別:97 | 在線時長:9851小時 | 升級還需:145小時級別:97 | 在線時長:9851小時 | 升級還需:145小時級別:97 | 在線時長:9851小時 | 升級還需:145小時
註冊日期: 2002-12-07
文章: 13341
精華: 0
現金: 26444 金幣
資產: 3024304 金幣
預設

這個人也蠻好奇的
所以做了以下實驗

先寫一段
語法:
#include <cstdlib>
#include <iostream>

using namespace std;

int main(int argc, char *argv[])
{
    int a=100,b=99;
    
    a^= b^= a ^= b;
    /* 寫的平民化一點就是
        a=a^b;
     b=a^b;
     a=a^b; */
    
    system("PAUSE");
    return EXIT_SUCCESS;
}
編譯後這裡用 OllyDbg 觀察
因為 99的16進制值 是 63
search一下就找到以下程式碼
語法:
MOV DWORD PTR SS:[EBP-4],64              ; |
MOV DWORD PTR SS:[EBP-8],63              ; |
MOV EDX,DWORD PTR SS:[EBP-8]             ; |
LEA EAX,DWORD PTR SS:[EBP-4]             ; |
XOR DWORD PTR DS:[EAX],EDX               ; |
MOV EDX,DWORD PTR SS:[EBP-4]             ; |
LEA EAX,DWORD PTR SS:[EBP-8]             ; |
XOR DWORD PTR DS:[EAX],EDX               ; |
MOV EDX,DWORD PTR SS:[EBP-8]             ; |
LEA EAX,DWORD PTR SS:[EBP-4]             ; |
XOR DWORD PTR DS:[EAX],EDX               ; |
MOV DWORD PTR SS:[ESP],專案1.00440000      ; |ASCII "PAUSE"
CALL <JMP.&msvcrt.system>                ; \system
乍看之下好像 沒有被做什麼多餘的加工

接著再來比較用暫存變數的方式
a^= b^= a ^= b;
改成
tmp=a; a=b; b=tmp;

同樣觀察編組譯後的組合語言碼
語法:
MOV DWORD PTR SS:[EBP-4],64              ; |
MOV DWORD PTR SS:[EBP-8],63              ; |
MOV EAX,DWORD PTR SS:[EBP-4]             ; |
MOV DWORD PTR SS:[EBP-C],EAX             ; |
MOV EAX,DWORD PTR SS:[EBP-8]             ; |
MOV DWORD PTR SS:[EBP-4],EAX             ; |
MOV EAX,DWORD PTR SS:[EBP-C]             ; |
MOV DWORD PTR SS:[EBP-8],EAX             ; |
MOV DWORD PTR SS:[ESP],專案1.00440000      ; |ASCII "PAUSE"
CALL <JMP.&msvcrt.system>                ; \system
同樣也是好像沒被做什麼多餘的加工的樣子


但仔細一看
原來還是編譯器在作祟
用暫存變數的方式之程式碼只花了 10行
而用互斥或方式之程式碼花了 13行
多出了 3行 LEA 機械碼
至於為何 互斥或方式 要用到 LEA呢 ?

LEA 指令:
語法:
LEA 是 80x86 CPU 指令集的一員,在 8086 CPU 就已經有這個指令,這個指令是用來取得變數的位址,其語法是:

LEA 暫存器,變數名
這個指令和 OFFSET 假指令的功能幾乎一樣,但是它是在 CPU 執行時,才取得變數的位址;而 OFFSET 則是在組譯時就取得位址。因此 LEA 可以用在變數位址可能會改變的情形,例如取得區域變數的位址。
使用 xor 編譯器會用到 堆疊節區 與 資料節區 (及暫存器)
可看出
堆疊節區 是用來做運算暫存的
資料節區 用來擺放結果

使用 mov 編譯器 只會用到 堆疊節區 (及暫存器)
資料在 堆疊節區 及 暫存器 之間移動

那一定要這麼做嗎? (xor)
這當然有它(編譯器)的理由
只是別人(編譯器)都這麼做了
除非你要 改寫編譯器 或 事後改寫機械碼(組合語言)
否則問這一句
沒有多大意義

當然 c/c++ 是很活的語言
你也可以自己寫 嵌入式組合語言在 c/c++裡
這樣編譯器就拿你沒則了
不過
在編譯後 有可能反而會再多出幾行 機械碼也不一定
(畢竟c/c++的嵌入式組合語言 編譯器 可不是照單全收的)

此帖於 2007-12-11 08:54 PM 被 mini 編輯. 原因: 打錯...
mini 目前離線  
送花文章: 2013, 收花文章: 8001 篇, 收花: 26805 次
回覆時引用此帖
有 2 位會員向 mini 送花:
Admin1 (2007-12-11),rank (2008-02-24)
感謝您發表一篇好文章