程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> 用匯編的眼光看C++ (之x86匯編)

用匯編的眼光看C++ (之x86匯編)

編輯:C++入門知識

 

【 聲明:版權所有,歡迎轉載,請勿用於商業用途。  聯系信箱:feixiaoxing @163.com】

 

 

    說到用匯編的眼光看C++語言,那麼怎麼閱讀匯編代碼就成了我們需要解決的一個問題。其實,實話說,匯編其實不難。只是我們需要明白這樣幾個問題:

 

    (1)匯編是什麼語言?

 

    (2)匯編中的主要內容有哪些?

 

    (3)匯編語言是怎麼和實際C/C++語言代碼一一對應的?

 

 

    (1)匯編是什麼語言

 

    其實匯編語言是CPU指令碼的一種標記符號。不同的CPU具有不同的指令集,普通PC上的CPU一般來自AMD或者是INTEL,使用的也就是我們今天所要說的X86指令集。其他類似的CPU還有POWERPC,主要來自電信企業的交換機、路由器使用;ARM類型,主要是智能終端或者儀器儀表類的設備使用;SUN SPARC類型,主要來供SUN服務器使用。因為CPU指令集和二進制碼幾乎是一一對應的,所以匯編語言不但可以幫助我們快速了解機器的硬件,也方便我們了解程序是怎麼在設備上面運行的。

 

 

    (2)匯編語言的內容有哪些?

 

    匯編語言的內容有很多,但是真正和我們C/C++語言相關的內容其實並不多。大體上你需要了解的只有寄存器、段地址、堆棧、寄存器之間的基本操作、地址訪問這些就足夠了。

 

 

    (3)匯編語言是怎麼和實際語言一一對應的?

 

    我們從一個范例開始。一般來說,一條語句都需要拆分成若干條匯編語句。比如說下面這一段話:

 

 

int m = 10; 

int n = 20; 

int p = m + n; 

 

    我們這裡假設m、n、p都是處在一個函數之中,所以事實上三個變量都是臨時變量,在進入到函數之前,ebp和esp之間都要騰出空間為這些臨時變量做准備。那麼這三句話應該是這樣解釋的。

 

43:       int m = 10; 

004012E8   mov         dword ptr [ebp-4],0Ah 

44:       int n = 20; 

004012EF   mov         dword ptr [ebp-8],14h 

45:       int p = m + n; 

004012F6   mov         eax,dword ptr [ebp-4] 

004012F9   add         eax,dword ptr [ebp-8] 

004012FC   mov         dword ptr [ebp-0Ch],eax 

    我們可以通過上面的代碼直觀地看到匯編語句和C語言之間的對應關系。第一句,m賦值為10,內存正是ebp向下的內存;第二句和第一句類似;第三句稍微有點復雜,我們可以分析一下。首先我們看到,CPU從堆棧中把m的數據找了出來,也就是[ebp-4]地址處的數據,接著CPU用同樣的方法把n的數據也找了出來,直接加到寄存器eax上,最後一步比較簡單,就是把eax的數據保存在[ebp-0c]處的地址上。只要是函數內部的臨時變量,就會看到這樣的形式。臨時變量就是依靠ebp的偏移地址獲取的。

    大家有沒有想過,如果p是全局變量呢?

 

 

45:       int m = 10; 

004012E8   mov         dword ptr [ebp-4],0Ah 

46:       int n = 20; 

004012EF   mov         dword ptr [ebp-8],14h 

47:       p = m + n; 

004012F6   mov         eax,dword ptr [ebp-4] 

004012F9   add         eax,dword ptr [ebp-8] 

004012FC   mov         [p (0042b0b4)],eax 

    看到上面的代碼,我們發現m、n的賦值方向沒有發生什麼樣的變化。變化的是,最後寄存器eax的數值被賦值給了一個絕對地址0x42b0b4。這說明了一個問題,在程序加載到內存後,全局變量是有獨立地址空間,並不會隨著堆棧的浮動發生變化。

    前面我們說過,在函數內部的所有變量都會保存在ebp到esp之間的堆棧空間上,那麼代碼是怎麼做的呢?我們可以看這樣一段匯編代碼?

 

 

41:   void process() 

42:   { 

004012D0   push        ebp 

004012D1   mov         ebp,esp 

004012D3   sub         esp,4Ch 

004012D6   push        ebx 

004012D7   push        esi 

004012D8   push        edi 

004012D9   lea         edi,[ebp-4Ch] 

004012DC   mov         ecx,13h 

004012E1   mov         eax,0CCCCCCCCh 

004012E6   rep stos    dword ptr [edi] 

43:       int m = 10; 

004012E8   mov         dword ptr [ebp-4],0Ah 

44:       int n = 20; 

004012EF   mov         dword ptr [ebp-8],14h 

45:       int p = m + n; 

004012F6   mov         eax,dword ptr [ebp-4] 

004012F9   add         eax,dword ptr [ebp-8] 

004012FC   mov         dword ptr [ebp-0Ch],eax 

46:   } 

    我們把剛才一段函數的完整代碼打印出來。我們發現,事實上在臨時變量m運算之前,函數做了很多預備操作,其主要目的有兩個:(1)為臨時變量准備空間;(2)對函數運算中使用到的寄存器進行保存處理,這是因為寄存器是所有函數共有的資源,如果不記錄好原來的數據,那麼在函數返回後,寄存器就會忘記原來的數值,不能在原來的狀態下繼續正確地運算了。從地址0x4012D0到地址0x4012E6之間共有10句話,其實意思並不困難。第一句,ebp壓棧;第二句esp復制給ebp;第三句esp自減4C大小,這個大小一般是按照函數內部定義了多少臨時變量決定的;第四句,ebx壓棧;第五句,esi壓棧;第六句,edi壓棧;第七句到第十句,把[ebp-4C]處向上的0x4C個字節全部設置成CC,edi為起始地址,ecx為循環次數0x13次,dword表示每次設置4個字節。

    那麼函數在返回前做了一些什麼呢?

 

 

46:   } 

004012FF   pop         edi 

00401300   pop         esi 

00401301   pop         ebx 

00401302   mov         esp,ebp 

00401304   pop         ebp 

00401305   ret 

    其實函數返回的時候,做的內容特別簡單。第一句edi出棧;第二句esi出棧;第三句ebx出棧,和前面寄存器進棧的順序是完全相反的。最後三句特別關鍵,我們看到ebp復制給esp,ebp出棧,函數返回,這樣一切都恢復到函數調用之前的狀態了。

    那麼函數調用的時候,入參是怎麼處理的呢?

 

 

53:       process(20); 

0040EFA4   push        14h 

0040EFA6   call        @ILT+40(process) (0040102d) 

0040EFAB   add         esp,4 

    上面一段代碼就是process函數含有一個參數時候的情形。函數調用後esp+4,堆棧恢復。堆棧+4,主要是因為參數的空間就是4個字節。所以用一幅圖說明一下,函數調用的時候堆棧空間應該是這樣的:

    |    函數參數      | 

 

    |    返回地址      |

    |    臨時變量      | <------------------------ ebp

 

    |    壓棧寄存器  |

 

    |    棧頂              | <-------------------------esp

   

 

其他知識:

 

    (1) 全局運算cpu寄存器很多,一般有eax,ebx,ecx,edx等等。我們通常說的ax,bx,cx,dx指的只是他們的低位部分。

 

    (2)段寄存器寄存了程序的代碼段,數據段和堆棧段。代碼段保存了全部的程序代碼,數據段保存了全據變量的代碼,而堆棧則是全部堆棧空間。

 

    (3)目前vc編譯器支持嵌入式匯編,大家有興趣的話可以在函數內部試試身手。下面的代碼只是一個范例:

 

 

void process(int* q) 

    _asm { 

        push eax 

        push ebx 

        push ecx 

        mov eax, 0x10 

        mov ebx, 0x15 

        add eax, ebx 

        mov ecx, q 

        mov  [ecx], eax 

        pop ecx 

        pop ebx 

        pop eax 

    } 

 

(全部完)

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved