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

用匯編的眼光看C++(開篇)

編輯:C++入門知識

 

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

   

 

    很多朋友,包括我自己在內,對C++語言的很多特性不是很明白。特別是幾年前找工作的時候,為了應付來自工作單位的考試,我經常逼著自己的去記住一些復雜的試題和答案。可是常常時間已過,一切又回到了原點。原來沒有弄清楚的問題還是沒有弄明白,一切都沒有發生改變。直到若干年後,當我在編碼過程中不斷積累經驗,嘗試用匯編代碼和內存數據來解釋一些現象的時候,才明白有些東西其實並不復雜。也許有的朋友對匯編語言會有畏懼,其實沒有必要。只要你對C語言有一些基礎,對堆棧有一些印象,那麼你已經擁有匯編語言的基礎了。在接下來的數篇博客中,我們就會就x86匯編、數據類型、數據運行邏輯、指針、數據、類、重載運算符在匯編下是如何展開的做一些介紹,談一些個人的看法。下面,我們就進行一些小測試,同時用匯編語言來說明一下。大家可以一起做一下。

 

 

(1)char name[] 和char* name

 

 

1: 

2:    void process() 

3:    { 

00401020   push        ebp 

00401021   mov         ebp,esp 

00401023   sub         esp,4Ch 

00401026   push        ebx 

00401027   push        esi 

00401028   push        edi 

00401029   lea         edi,[ebp-4Ch] 

0040102C   mov         ecx,13h 

00401031   mov         eax,0CCCCCCCCh 

00401036   rep stos    dword ptr [edi] 

4:        char name_tmp[] = {"hello"}; 

00401038   mov         eax,[string "hello" (0042201c)] 

0040103D   mov         dword ptr [ebp-8],eax 

00401040   mov         cx,word ptr [string "hello"+4 (00422020)] 

00401047   mov         word ptr [ebp-4],cx 

5:        char* name_glb = "hello"; 

0040104B   mov         dword ptr [ebp-0Ch],offset string "hello" (0042201c) 

6:    } 

00401052   pop         edi 

00401053   pop         esi 

00401054   pop         ebx 

00401055   mov         esp,ebp 

00401057   pop         ebp 

00401058   ret 

    通過上面的代碼,我們可以清楚地看出兩者之間的差別。"hello"字符串是一個全局只讀變量,空間地址為0x0042201C。name_tmp是函數內的char數組,第4行語句下面四行表示全局數據“hello”是分兩次拷貝到name_tmp的,第一次是dword、四個字節,第二次是word、兩個字節。所以name_tmp共有6個字節。相比較而言,name_glb什麼也沒有,它只是把自己指向了全局變量而已,所以它只是一個指針而已。

(2)apple a()和apple b

 

假設class apple的定義為:

 

 

class apple 

public: 

    apple() {} 

    ~apple() {} 

}; 

那麼apple a()和apple b是分別怎麼編譯的呢?

 

 

9:    void process() 

10:   { 

00401020   push        ebp 

00401021   mov         ebp,esp 

00401023   sub         esp,44h 

00401026   push        ebx 

00401027   push        esi 

00401028   push        edi 

00401029   lea         edi,[ebp-44h] 

0040102C   mov         ecx,11h 

00401031   mov         eax,0CCCCCCCCh 

00401036   rep stos    dword ptr [edi] 

11:       apple a(); 

12:       apple b; 

00401038   lea         ecx,[ebp-4] 

0040103B   call        @ILT+20(apple::apple) (00401019) 

13:   } 

00401040   lea         ecx,[ebp-4] 

00401043   call        @ILT+10(apple::~apple) (0040100f) 

00401048   pop         edi 

00401049   pop         esi 

0040104A   pop         ebx 

0040104B   add         esp,44h 

0040104E   cmp         ebp,esp 

00401050   call        __chkesp (004010b0) 

00401055   mov         esp,ebp 

00401057   pop         ebp 

00401058   ret 

 

    為什麼apple a()這邊什麼也沒有編譯呢?原因很簡單,因為編譯器把apple a()看成是一個extern的函數,返回值為apple。與此相對應的apple b才是函數中真正定義的臨時變量,因為在下面不遠處有apple的兩個函數——apple的構造函數和apple的析構函數哦。

(3)(apple*) (0) -> print()

 

其中class apple這樣定義:

 

 

class apple 

    int value; 

public: 

    apple() {} 

    ~apple() {} 

    void print() { return;}  

}; 

    如果0設置為apple*,那麼訪問函數print會有問題嗎?

 

 

10:   void process() 

11:   { 

00401030   push        ebp 

00401031   mov         ebp,esp 

00401033   sub         esp,40h 

00401036   push        ebx 

00401037   push        esi 

00401038   push        edi 

00401039   lea         edi,[ebp-40h] 

0040103C   mov         ecx,10h 

00401041   mov         eax,0CCCCCCCCh 

00401046   rep stos    dword ptr [edi] 

12:       ((apple*)(0))->print(); 

00401048   xor         ecx,ecx 

0040104A   call        @ILT+0(apple::print) (00401005) 

13:   } 

0040104F   pop         edi 

00401050   pop         esi 

00401051   pop         ebx 

00401052   add         esp,40h 

00401055   cmp         ebp,esp 

00401057   call        __chkesp (004010e0) 

0040105C   mov         esp,ebp 

0040105E   pop         ebp 

0040105F   ret 

    通過運行函數,我們發現沒有任何異常產生,為什麼呢?因為我們發現ecx是作為0傳給print函數的,也就是我們熟悉的this指針為0。但是我們發現在print函數內部沒有用到this指針,因為我們根本沒有對this->value進行訪問,只是一個返回語句return。這說明指針作為class null指針並不可怕,可怕的是用null去訪問內存中的數據。

(4)int m = 1; int n = m++ + ++m; 那麼n是多少呢?

 

 

10:   void process() 

11:   { 

0040D4D0   push        ebp 

0040D4D1   mov         ebp,esp 

0040D4D3   sub         esp,48h 

0040D4D6   push        ebx 

0040D4D7   push        esi 

0040D4D8   push        edi 

0040D4D9   lea         edi,[ebp-48h] 

0040D4DC   mov         ecx,12h 

0040D4E1   mov         eax,0CCCCCCCCh 

0040D4E6   rep stos    dword ptr [edi] 

12:       int m = 1; 

0040D4E8   mov         dword ptr [ebp-4],1 

13:       int n = m++ + ++m; 

0040D4EF   mov         eax,dword ptr [ebp-4] 

0040D4F2   add         eax,1 

0040D4F5   mov         dword ptr [ebp-4],eax 

0040D4F8   mov         ecx,dword ptr [ebp-4] 

0040D4FB   add         ecx,dword ptr [ebp-4] 

0040D4FE   mov         dword ptr [ebp-8],ecx 

0040D501   mov         edx,dword ptr [ebp-4] 

0040D504   add         edx,1 

0040D507   mov         dword ptr [ebp-4],edx 

14:   } 

0040D50A   pop         edi 

0040D50B   pop         esi 

0040D50C   pop         ebx 

0040D50D   mov         esp,ebp 

0040D50F   pop         ebp 

    通過匯編代碼,我們看到【ebp-4】就是m在堆棧中的地址,【ebp-8】就是n在堆棧中的地址。int n = m++ + ++m下面總共有9句匯編。我們可以分析一下:前面三句表示m自己增加1,第四句表示ecx = m,即ecx = 2。第五句ecx和m相加,翻譯過來就是ecx = ecx + m。此時ecx = 4。第六句表示n = ecx。 第七句到第九句表示m自增加1。為什麼會出現這樣的情況呢,其實道理很簡單,主要是因為我們的表達式是從右向左運算的。如果大家這樣看就明白了,首先++m,然後n = m + m,最後m++。

(5)*p++和(*p)++區別是什麼

 

 

10:   void process() 

11:   { 

0040D4D0   push        ebp 

0040D4D1   mov         ebp,esp 

0040D4D3   sub         esp,48h 

0040D4D6   push        ebx 

0040D4D7   push        esi 

0040D4D8   push        edi 

0040D4D9   lea         edi,[ebp-48h] 

0040D4DC   mov         ecx,12h 

0040D4E1   mov         eax,0CCCCCCCCh 

0040D4E6   rep stos    dword ptr [edi] 

12:       char data = 'a'; 

0040D4E8   mov         byte ptr [ebp-4],61h 

13:       char* p = & data; 

0040D4EC   lea         eax,[ebp-4] 

0040D4EF   mov         dword ptr [ebp-8],eax 

14:       *p++; 

0040D4F2   mov         ecx,dword ptr [ebp-8] 

0040D4F5   add         ecx,1 

0040D4F8   mov         dword ptr [ebp-8],ecx 

15:       (*p)++; 

0040D4FB   mov         edx,dword ptr [ebp-8] 

0040D4FE   mov         al,byte ptr [edx] 

0040D500   add         al,1 

0040D502   mov         ecx,dword ptr [ebp-8] 

0040D505   mov         byte ptr [ecx],al 

16:   } 

0040D507   pop         edi 

0040D508   pop         esi 

0040D509   pop         ebx 

0040D50A   mov         esp,ebp 

0040D50C   pop         ebp 

0040D50D   ret 

    我們首先創建局部變量data。然後把data的指針復制給p。從匯編代碼可以清楚的看出來:*p++就等於p++;(*p)++首先把指針復制給edx,然後獲取edx地址指向的char數據復制給al,al自增加1,同時p地址復制給ecx,al復制給ecx指向的地址,就是這麼簡單。

 

類似的問題還有很多,大家不妨自己試一試:

 

(1) 下面的union在內存是怎麼安排的?gcc和vc編譯的時候,分配的內存size是一樣的嗎?

 

 

typedef union  

char m:3; 

char n:7; 

int data; 

}value; 

(2) 下面地址一致嗎?

 

 

char value1[] = {"hello"}; 

char value2[] = {"hello"}; 

char* pValue1 = “hello”; 

char* pValue2 = "hello"; 

value1和value2地址一致嗎?pValue1和pValue2呢? 

(3)下面一段話為什麼運行錯誤?為什麼內存洩露了?怎麼修改?

 

 

class apple 

    char* pName; 

public: 

    apple() { pName = (char*)malloc(10);} 

    ~apple() {if(NULL != pName) free(pName);} 

}; 

 

void process() 

    apple a, b;  

    a = b; 

 

(全文完)

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