這個人也蠻好奇的
所以做了以下實驗
先寫一段
語法:
#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++的嵌入式組合語言 編譯器 可不是照單全收的)