轉C/C++和彙編混合編程

來源:互聯網
上載者:User

轉C和彙編混合編程

.data是初始化的資料區塊。這些資料包括編譯時間被初始化的globle和static變數,也包括字串。
連接器將OBJs及LIBs檔案的.data結合成一個大的.data。local變數以放在一個線性堆棧中,
不佔.data和.bss的空間。和.text一樣,資料區塊是以明文的形式存放在檔案中的。無法防止對其物理的修改。
.bss區是存放未初始化全域和靜態變數的。

在C和彙編混合編程的時候,存在C語言和組合語言的變數以及函數的介面問題。
在C程式中定義的變數,編譯為.asm檔案後,都被放進了.bss區,而且變數名的前面都帶了一個底線。在C程式中定義的函數,編譯後在函數名前也帶了一個底線。例如:

extern int num就會變成 .bss _num, 1
extern float nums[5]就會變成.bss _nums, 5
extern void func ( )就會變成 _func,

 函式宣告: C中在函數前加extern聲明此函數為外部函數,在彙編中要聲明函數名為全域變數,如:
   extern void delay(void) ;  /*in C*/
   globl delay   ; in asm
 _delay:   ; delay function begins

(1) 組譯工具中訪問c程式中的變數和函數。
在組譯工具中,用_XX就可以訪問C中的變數XX了。訪問數組時,可以用_XX+位移量來訪問,如_XX+3訪問了數組中的XX[3]。

    在組譯工具調用C函數時,如果沒有參數傳遞,直接用_funcname 就可以了。如果有參數傳遞, 則函數中最左邊的一個參數由寄存器A給出,其他的參數按順序由堆棧給出。傳回值是返回到A寄存器或者由A寄存器給出的地址。同時注意,為了能夠讓組合語言能訪問到C語言中定義的變數和函數,他們必須聲明為外部變數,即加extern 首碼。

(2) c程式中訪問組譯工具中的變數

    如果需要在c程式中訪問組譯工具中的變數,則組譯工具中的變數名必須以底線為首字元,並用global使之成為全域變數。

    如果需要在c程式中調用組譯工具中的過程,則過程名必須以底線為首字元,並且,要根據c程式編譯時間使用的模式是stack-based model還是register argument model來正確地編寫該過程,使之能正確地取得調用參數。

(3) 線上彙編

    在C程式中直接插入 asm(“ *** ”),內嵌彙編語句,需要注意的是這種用法要慎用,線上彙編提供了能直接讀寫硬體的能力,如讀寫中斷控制允許寄存器等,但編譯器並不檢查和分析線上組合語言,插入線上組合語言改變彙編環境或可能改變C變數的值可能導致嚴重的錯誤。

彙編和C介面中定址方式的改變:

    需要注意的是,在C語言中,對於局部變數的建立和訪問,是通過堆棧實現的,它的定址是通過堆棧寄存器SP實現的。而在組合語言中,為了使程式碼變得更為精簡,TI在直接定址方式中,地址的低7位直接包含在指令中,這低7位所能定址的具體位置可由DP寄存器或SP寄存器決定。具體實現可通過設定ST1寄存器的CPL位實現,CPL=0,DP定址,CPL=1,SP定址。在DP定址的時候,由DP提供高9位地址,與低7位組成16位地址;在SP定址的時候,16位地址是由SP(16位)與低7位直接相加得來。

    由於在C語言的環境下,局部變數的定址必須通過SP寄存器實現,在混合編程的時候,為了使組合語言不影響堆棧寄存器SP,通常的方式是在彙編環境中使用DP方式定址,這樣可以使二者互不干擾。編程中只要注意對CPL位正確設定即可

 

轉C++與彙編混合編程C/C++都是非常貼近最終彙編實現的語言,它們可以很容易地和彙編共存。通常一個函數會被編譯成一段彙編代碼,函數指標就是指向這段代碼的地址。函數參數是通過把資料壓入堆棧傳遞給函數的,在C++裡,為了提高效率,通常會把this指標置入ECX寄存器來傳入函數,因為寄存器的訪問速度要優於記憶體。而函數在運行時,編譯器產生的代碼通常並不會用POP指令將傳入的參數出棧,而是修改EBP的值,指向當前堆棧,然後通過對EBP的間接定址來訪問參數,這樣,在多層函數調用中,使得每層函數的參數都得到保護。同樣,函數用到的局部變數也會被放入堆棧中,通過EBP來讀些。當函數返回時,C語言的預設做法是讓調用者去處理傳入的參數佔據著堆棧的問題,而C++的預設做法是函數本身從堆棧中去掉這些參數。
函數的傳回值一般會放入EAX寄存器中,某些情況會用到EDX。如果返回的資料過大,比如是一個類或結構,許多編譯器的做法是把傳回值需要的資料空間有調用者在堆棧中分配好,把這個臨時變數的指標傳入函數,函數把結果填入指標指向的記憶體。
我們自己寫彙編代碼時如果想調用一個C++語言函數,則在調用前,如果EAX,ECX,EDX內的資料對你的程式上下文有意義,那麼請自己提前儲存起來(C語言可以不儲存ECX),而EBX,EDI,ESI,EBP這些寄存器的資料不會被破壞,ESP,EIP則會按約定的邏輯起變化。同樣如果想用彙編寫一個可供C++調用的函數,那麼請遵守同樣的約定。你的函數如果用到EBX,ESI,EDI等寄存器,請在函數入口處儲存,並在函數返回前恢複。而EAX,ECX,EDX則可以任意使用(C環境下,ECX例外,需要得到和EBX一樣的保護)。

 

 

轉VC7中彙編和C++混合的初步心得

1 查看編譯器輸出

通常來說,Debug 模式單步跟蹤時Alt+8 就可以看見彙編代碼。問題是 Debug 只是代表了一個側面,並不代表最終的 Release ;另一方面 Debug 模式包含了些許額外的測試代碼 —— 恩,可能代碼有些多...天啊,他們幹嘛要加、那麼多、莫名其妙的代碼混淆視聽阿!

好嘛,看看簡潔的Release模式 —— orz.... 不能單步跟蹤C++程式了? 連main函數在哪裡都看不見... 瞎了...

Release 模式單步跟蹤要需要高深的技術底氣。不過也沒那麼絕,要看 Release 模式的輸出,我們可以在項目屬性->C/C++->輸出檔案頁面中把“彙編輸出”項定為“帶原始碼的程式集(/FAs)”。這樣,在Release目錄下就可以看見對應的asm檔案了。看asm檔案,唯一的缺點是不能單步跟蹤研究。

這個asm檔案搞不好會非常大——主要是由於C++標準庫廣泛使用模板的原因,若我們放棄C++庫一律使用C標準庫就會看到很乾淨的asm檔案(同時會看見一個1/4大小的可執行檔,你會明白為什麼那麼多人支援C )——當然這不是C++的幹活。 要在這個動輒數W行的檔案中裡面找原始碼對應的彙編,推薦大家找一行一定不會被最佳化掉的代碼(沒錯,某些代碼可能人間蒸發),直接F3搜尋。

asm中包含了很多注釋,有基本的彙編知識然後連蒙帶猜就能看懂了。一對挺有用的標誌是:

_TEXT SEGMENT // 程式碼片段開始標誌
_TEXT ENDS // 程式碼片段結束標誌

對於觀察每個函數的產生代碼來說,這兩個標誌能起到路標的作用。

 

2 彙編訪問類成員

若有一個類

class A{
int _i;
};

有A 的執行個體a,下面的代碼令 a._i = 10,這隻需要一個指令:

__asm mov [a]A._i, 10

但是在A 的成員函數中怎麼辦呢?

我們知道,成員函數調用為 thiscall, this通過 ecx傳遞。所以在函數的開頭現場尚未被破壞的時候,可以直接用 ecx 變址訪問。如下面是一個常見的set函數, 它令 A::_i = n (注意mov等指令中,兩個運算元不能同時為記憶體內容,所以必須用寄存器eax接力):

inline void A::i( int n ){
__asm mov eax, n
__asm mov [ecx]A._i, eax
}

不過這有兩個問題。一來,ecx並非總是this;它隨時可能被刷掉。在某個不能確定儲存this寄存器的時候,你需要手動寫ecx:

__asm mov ecx, this
__asm mov eax, n
__asm mov [ecx]A._i, eax

這樣寫會迫使編譯器把this的值複製到棧上 —— 而一般來說對於小函數而言,編譯器會盡量只用寄存器。這可能是一個額外的小小開銷。(注意,千萬不要以為可以這麼訪問: [this]A._i )

另一方面,雖然在我們的確寫了大大的“ inline ”幾個字,但是看看輸出代碼——你會發現:任何包含了內嵌彙編的 inline 成員函數都不會被內聯!

 

3 彙編/內嵌函式和效率

普通函數是可以內聯的,下面就是一個完美的結合 C++/ asm 的例子:

inline long long getTimer(){
long long time;
__asm rdtsc
__asm mov DWORD PTR time, eax
__asm mov DWORD PTR time + 4, edx
return time;
}

rdtsc指令用來獲得CPU自開機啟動並執行刻度數。它的結果是64位的,儲存到 eax 和 edx兩個寄存器中,可以用來精確測量演算法開銷。上面的函數內聯之後, 局部變數不見了, 臨時傳回值也不見了,只有最核心的三行代碼,沒有比這更簡潔的了:

; 68 : long long b = getTimer();

rdtsc
mov DWORD PTR _time$11298[ebp], eax
mov DWORD PTR _time$11298[ebp+4], edx

成員函數內聯則又是另一個故事:系統不知道如何處理this,所以他乾脆忽略所有內嵌asm成員函數的內聯標誌。

好嘛,VC不願上,我們用皮鞭趕著他上! 把第二部分最初那個 A::i 改為 __forceinline 就強制內聯了——也就是強制VC犯錯誤了:不幸的編譯器看不懂我們的代碼,只好把指令抄到函數調用處。他不曉得初始化ecx,那個mov可能往任何地方寫內容——比如把你的開機密碼寫到案頭上——

雖然可以手動設定ecx,不過我們可不希望看見如此醜陋的調用(想象一下你的同事看到這段代碼的困惑):

__asm lea ecx, a
a.i( 20 );

要正確編寫能成功內聯的代碼必須結合另一個方案,手動複製this:

__forceinline void A::i( int n ){
__asm mov ecx, this
__asm mov eax, n
__asm mov [ecx]A._i, eax
}

厄。。。猜猜看結果如何?

首先看看我們直接用C++寫一個 set函數 (譬如 void A::i( int n ){ _i = n; } )內聯後的結果:

; 56 : a.i( 5 );

mov DWORD PTR _a$[ebp+8], 5

最殘酷的結果也只需一句mov。 更可能的結果是——他被最佳化得連影兒都看不見。
然後看看我們的三年懷胎含辛茹苦研究出來的混合彙編的內聯:

; 56 : a.i( 5 );

lea eax, DWORD PTR _a$[ebp]
pop ecx
mov DWORD PTR $T11194[ebp], eax
mov ecx, DWORD PTR $T11194[ebp]
mov eax, 5
mov DWORD PTR [ecx+8], eax

這麼長啊....生出一個怪胎... VC 中嵌入彙編的一個壞處是:編譯器很難將他和C++協調,很難最佳化他。

彙編最佳化可以很快速、很強,但是一定要慎用。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.