天天看點

用彙編的眼光看C++(之算術符重載)14

【 聲明:版權所有,歡迎轉載,請勿用于商業用途。】

    算術符重載是類的有一個特性,但是每個人使用的方法不一樣。用的好,則事半功倍;但是如果不正确的使用,則會後患無窮。

    (1) 簡單算術符介紹

    那什麼是算術符重載呢?我們可以舉個例子。一般來說,我們定義兩個int類型的變量的話,我們就可應對這兩個類型進行加、減、乘、除的操作,同時還能比 較判斷、列印、數組操作、*号操作等等。那麼如果我們想自己定義的類也具有這樣的屬性,那我們應該怎麼辦呢?當然就要算術符重載了。首先,我們對基本 class做一個定義:

[cpp] view plaincopy

  1. class desk  
  2. {  
  3. public:  
  4.     int price;  
  5.     desk(int value):price(value) {}  
  6.     ~desk() {}  
  7.     desk& operator+= (desk& d){  
  8.         this->price += d.price;  
  9.         return *this;  
  10.     }  
  11. };  

    下面,可以用一個範例函數說明一下使用的方法:

  1. 74:       desk n(5);  
  2. 0040126D   push        5  
  3. 0040126F   lea         ecx,[ebp-10h]  
  4. 00401272   call        @ILT+0(desk::desk) (00401005)  
  5. 00401277   mov         dword ptr [ebp-4],0  
  6. 75:       desk m(10);  
  7. 0040127E   push        0Ah  
  8. 00401280   lea         ecx,[ebp-14h]  
  9. 00401283   call        @ILT+0(desk::desk) (00401005)  
  10. 00401288   mov         byte ptr [ebp-4],1  
  11. 76:       n += m;  
  12. 0040128C   lea         eax,[ebp-14h]  
  13. 0040128F   push        eax  
  14. 00401290   lea         ecx,[ebp-10h]  
  15. 00401293   call        @ILT+40(desk::operator+=) (0040102d)  
  16. 77:   }  

    大家可以把重點放在76句上面,不過74、75句我們也會稍微介紹一下:

    74句: 建立desk類型的臨時變量n,調用構造函數

    75句: 建立desk類型的臨時變量m,調用構造函數

    76句: 兩個desk類型的資料相加,但是在彙編的形式上面,我們發現編譯器把這段代碼解釋成函數調用,也就是我們在上面定義的算術符重載函數。

     (2)new、free重載

     在C++裡面,我們不光可以對普通的算術符進行重載處理,還能對new、free進行重載。通過重載new、free,我們還可以加深對代碼的認識,正确認識構造、析構、堆記憶體配置設定的原理。

    首先,我們對new和delete進行重載定義:

  1.     void* operator new(size_t size) {return malloc(size);}  
  2.     void operator delete (void* pData) { if(NULL != pData) free(pData);}  

    那麼使用呢?

  1. 72:       desk* d =  new desk(10);  
  2. 0040127D   push        4  
  3. 0040127F   call        @ILT+65(desk::operator new) (00401046)  
  4. 00401284   add         esp,4  
  5. 00401287   mov         dword ptr [ebp-18h],eax  
  6. 0040128A   mov         dword ptr [ebp-4],0  
  7. 00401291   cmp         dword ptr [ebp-18h],0  
  8. 00401295   je          process+56h (004012a6)  
  9. 00401297   push        0Ah  
  10. 00401299   mov         ecx,dword ptr [ebp-18h]  
  11. 0040129C   call        @ILT+5(desk::desk) (0040100a)  
  12. 004012A1   mov         dword ptr [ebp-24h],eax  
  13. 004012A4   jmp         process+5Dh (004012ad)  
  14. 004012A6   mov         dword ptr [ebp-24h],0  
  15. 004012AD   mov         eax,dword ptr [ebp-24h]  
  16. 004012B0   mov         dword ptr [ebp-14h],eax  
  17. 004012B3   mov         dword ptr [ebp-4],0FFFFFFFFh  
  18. 004012BA   mov         ecx,dword ptr [ebp-14h]  
  19. 004012BD   mov         dword ptr [ebp-10h],ecx  
  20. 73:       delete d;  
  21. 004012C0   mov         edx,dword ptr [ebp-10h]  
  22. 004012C3   mov         dword ptr [ebp-20h],edx  
  23. 004012C6   mov         eax,dword ptr [ebp-20h]  
  24. 004012C9   mov         dword ptr [ebp-1Ch],eax  
  25. 004012CC   cmp         dword ptr [ebp-1Ch],0  
  26. 004012D0   je          process+91h (004012e1)  
  27. 004012D2   push        1  
  28. 004012D4   mov         ecx,dword ptr [ebp-1Ch]  
  29. 004012D7   call        @ILT+0(desk::`scalar deleting destructor') (00401005)  
  30. 004012DC   mov         dword ptr [ebp-28h],eax  
  31. 004012DF   jmp         process+98h (004012e8)  
  32. 004012E1   mov         dword ptr [ebp-28h],0  
  33. 74:   }  

    上面是一段普通的new、delete使用代碼。但是我們發現,簡單的一個語句,在彙編器看來,卻需要做這麼多的内容,這是為什麼呢,我們不妨來自習看一看:

    72句:彙編中有兩個函數調用,一個是new調用,也就是我們重定義的new函數,一個是構造函數,最後的幾行代碼主要是把構造函數傳回指針指派給一些臨時變量,可忽略

    73句:彙編中首先讓指針和0進行了判斷,然後調用了一個函數,似乎沒有調用我們的delete函數,我們可以跟進去看一下:

  1. desk::`scalar deleting destructor':  
  2. 00401410   push        ebp  
  3. 00401411   mov         ebp,esp  
  4. 00401413   sub         esp,44h  
  5. 00401416   push        ebx  
  6. 00401417   push        esi  
  7. 00401418   push        edi  
  8. 00401419   push        ecx  
  9. 0040141A   lea         edi,[ebp-44h]  
  10. 0040141D   mov         ecx,11h  
  11. 00401422   mov         eax,0CCCCCCCCh  
  12. 00401427   rep stos    dword ptr [edi]  
  13. 00401429   pop         ecx  
  14. 0040142A   mov         dword ptr [ebp-4],ecx  
  15. 0040142D   mov         ecx,dword ptr [ebp-4]  
  16. 00401430   call        @ILT+75(desk::~desk) (00401050)  
  17. 00401435   mov         eax,dword ptr [ebp+8]  
  18. 00401438   and         eax,1  
  19. 0040143B   test        eax,eax  
  20. 0040143D   je          desk::`scalar deleting destructor'+3Bh (0040144b)  
  21. 0040143F   mov         ecx,dword ptr [ebp-4]  
  22. 00401442   push        ecx  
  23. 00401443   call        @ILT+80(desk::operator delete) (00401055)  
  24. 00401448   add         esp,4  
  25. 0040144B   mov         eax,dword ptr [ebp-4]  
  26. 0040144E   pop         edi  
  27. 0040144F   pop         esi  
  28. 00401450   pop         ebx  
  29. 00401451   add         esp,44h  
  30. 00401454   cmp         ebp,esp  
  31. 00401456   call        __chkesp (00408810)  
  32. 0040145B   mov         esp,ebp  
  33. 0040145D   pop         ebp  
  34. 0040145E   ret         4  

    上面的代碼便是跟到0x401005之後遇到的代碼,這裡有一個跳轉,真正函數開始的地方是0x401410。這裡我們發現函數實際上還是調用了我們定 義的delete函數和desk的析構函數。隻不過析構函數一定要放在delete調用之前。是以,這裡我們就看到了,c++中new的真正含義就是先分 配記憶體,然後調用構造函數;而delete則是先對變量進行析構處理,然後free記憶體,這就是new和delete的全部意義。掌握了這個基礎,可以幫 助我們本地對記憶體進行很好的管理。

    (3)friend算術符重載和普通算術符重載的差別

    有一種算術符的重載是這樣的:

  1.     friend desk operator+ (desk& d1, desk& d2);  
  2. desk operator +(desk& d1, desk& d2)  
  3.     desk d(0);  
  4.     d.price = d1.price + d2.price;  
  5.     return d;  
  6. }  
  7. void process()  
  8.     desk d1(3);  
  9.     desk d2(4);  
  10.     desk d = d1 + d2;  
  11.     return;  

    感興趣的同學可以彙編看一下,找一找它和普通的非友元函數有哪些差別。不過上面的代碼還是讓我們看出了一些端倪:

    a)友元函數不屬于類,因為定義的時候我們發現沒有desk::這樣的字首

    b)友元算術符重載需要比普通的算術符重載多一個輸入參數

    c)友元函數在進行算術重載定義的時候需要多定義一個臨時變量d,這在函數operator+()可以看出來

    d)友元算術重載函數會破壞原來類地封裝性

    e)友元函數實際上就是全局函數

算術運算符使用的經驗總結:

    (1)算術重載函數是一把雙刃劍,務必小心使用

    (2)内部算術符函數優先使用于非友元函數

    (3)遇到 = 号重載特别注意一下指針

    (4)重載的時候函數的内容要和重載的運算符一緻,不用重載的是+,實際運算的是相減的内容

    (5)除非特别需要重載,負責别重載

    (6)重載的時候多複用已經存在的重載運算符

    (7)new、delete除了記憶體管理和測試,一般不重載,全局new、delete嚴謹重載

    (8)相關運算符重載要在stl中使用,務必注意傳回值

【預報: 下面部落格開始介紹const屬性的一些内容】