用汇编的眼光看C++(之算术符重载)

来源:岁月联盟 编辑:exp 时间:2011-10-06

 

【 声明:版权所有,欢迎转载,请勿用于商业用途。  联系信箱:feixiaoxing @163.com】

 

 

 

 

 

    算术符重载是类的有一个特性,但是每个人使用的方法不一样。用的好,则事半功倍;但是如果不正确的使用,则会后患无穷。

 

    (1) 简单算术符介绍

 

    那什么是算术符重载呢?我们可以举个例子。一般来说,我们定义两个int类型的变量的话,我们就可应对这两个类型进行加、减、乘、除的操作,同时还能比较判断、打印、数组操作、*号操作等等。那么如果我们想自己定义的类也具有这样的属性,那我们应该怎么办呢?当然就要算术符重载了。首先,我们对基本class做一个定义:

 

 

class desk 

public: 

    int price; 

 

    desk(int value):price(value) {} 

    ~desk() {} 

    desk& operator+= (desk& d){ 

        this->price += d.price; 

        return *this; 

    } 

}; 

class desk

{

public:

       int price;

 

       desk(int value):price(value) {}

       ~desk() {}

       desk& operator+= (desk& d){

              this->price += d.price;

              return *this;

       }

};

    下面,可以用一个范例函数说明一下使用的方法:

 

74:       desk n(5); 

0040126D   push        5 

0040126F   lea         ecx,[ebp-10h] 

00401272   call        @ILT+0(desk::desk) (00401005) 

00401277   mov         dword ptr [ebp-4],0 

75:       desk m(10); 

0040127E   push        0Ah 

00401280   lea         ecx,[ebp-14h] 

00401283   call        @ILT+0(desk::desk) (00401005) 

00401288   mov         byte ptr [ebp-4],1 

76:       n += m; 

0040128C   lea         eax,[ebp-14h] 

0040128F   push        eax 

00401290   lea         ecx,[ebp-10h] 

00401293   call        @ILT+40(desk::operator+=) (0040102d) 

77:   } 

74:       desk n(5);

0040126D   push        5

0040126F   lea         ecx,[ebp-10h]

00401272   call        @ILT+0(desk::desk) (00401005)

00401277   mov         dword ptr [ebp-4],0

75:       desk m(10);

0040127E   push        0Ah

00401280   lea         ecx,[ebp-14h]

00401283   call        @ILT+0(desk::desk) (00401005)

00401288   mov         byte ptr [ebp-4],1

76:       n += m;

0040128C   lea         eax,[ebp-14h]

0040128F   push        eax

00401290   lea         ecx,[ebp-10h]

00401293   call        @ILT+40(desk::operator+=) (0040102d)

77:   }

    大家可以把重点放在76句上面,不过74、75句我们也会稍微介绍一下:

 

    74句: 创建desk类型的临时变量n,调用构造函数

 

    75句: 创建desk类型的临时变量m,调用构造函数

 

    76句: 两个desk类型的数据相加,但是在汇编的形式上面,我们发现编译器把这段代码解释成函数调用,也就是我们在上面定义的算术符重载函数。

 

     (2)new、free重载

 

     在C++里面,我们不光可以对普通的算术符进行重载处理,还能对new、free进行重载。通过重载new、free,我们还可以加深对代码的认识,正确认识构造、析构、堆内存分配的原理。

 

    首先,我们对new和delete进行重载定义:

 

 

class desk 

public: 

    int price; 

 

    desk(int value):price(value) {} 

    ~desk() {} 

    void* operator new(size_t size) {return malloc(size);} 

    void operator delete (void* pData) { if(NULL != pData) free(pData);} 

}; 

class desk

{

public:

       int price;

 

       desk(int value):price(value) {}

       ~desk() {}

       void* operator new(size_t size) {return malloc(size);}

       void operator delete (void* pData) { if(NULL != pData) free(pData);}

};    那么使用呢?

 

 

72:       desk* d =  new desk(10); 

0040127D   push        4 

0040127F   call        @ILT+65(desk::operator new) (00401046) 

00401284   add         esp,4 

00401287   mov         dword ptr [ebp-18h],eax 

0040128A   mov         dword ptr [ebp-4],0 

00401291   cmp         dword ptr [ebp-18h],0 

00401295   je          process+56h (004012a6) 

00401297   push        0Ah 

00401299   mov         ecx,dword ptr [ebp-18h] 

0040129C   call        @ILT+5(desk::desk) (0040100a) 

004012A1   mov         dword ptr [ebp-24h],eax 

004012A4   jmp         process+5Dh (004012ad) 

004012A6   mov         dword ptr [ebp-24h],0 

004012AD   mov         eax,dword ptr [ebp-24h] 

004012B0   mov         dword ptr [ebp-14h],eax 

004012B3   mov         dword ptr [ebp-4],0FFFFFFFFh 

004012BA   mov         ecx,dword ptr [ebp-14h] 

004012BD   mov         dword ptr [ebp-10h],ecx 

73:       delete d; 

004012C0   mov         edx,dword ptr [ebp-10h] 

004012C3   mov         dword ptr [ebp-20h],edx 

004012C6   mov         eax,dword ptr [ebp-20h] 

004012C9   mov         dword ptr [ebp-1Ch],eax 

004012CC   cmp         dword ptr [ebp-1Ch],0 

004012D0   je          process+91h (004012e1) 

004012D2   push        1 

004012D4   mov         ecx,dword ptr [ebp-1Ch] 

004012D7   call        @ILT+0(desk::`scalar deleting destructor') (00401005) 

004012DC   mov         dword ptr [ebp-28h],eax 

004012DF   jmp         process+98h (004012e8) 

004012E1   mov         dword ptr [ebp-28h],0 

74:   } 

72:       desk* d =  new desk(10);

0040127D   push        4

0040127F   call        @ILT+65(desk::operator new) (00401046)

00401284   add         esp,4

00401287   mov         dword ptr [ebp-18h],eax

0040128A   mov         dword ptr [ebp-4],0

00401291   cmp         dword ptr [ebp-18h],0

00401295   je          process+56h (004012a6)

00401297   push        0Ah

00401299   mov         ecx,dword ptr [ebp-18h]

0040129C   call        @ILT+5(desk::desk) (0040100a)

004012A1   mov         dword ptr [ebp-24h],eax

004012A4   jmp         process+5Dh (004012ad)

004012A6   mov         dword ptr [ebp-24h],0

004012AD   mov         eax,dword ptr [ebp-24h]

004012B0   mov         dword ptr [ebp-14h],eax

004012B3   mov         dword ptr [ebp-4],0FFFFFFFFh

004012BA   mov         ecx,dword ptr [ebp-14h]

004012BD   mov         dword ptr [ebp-10h],ecx

73:       delete d;

004012C0   mov         edx,dword ptr [ebp-10h]

004012C3   mov         dword ptr [ebp-20h],edx

004012C6   mov         eax,dword ptr [ebp-20h]

004012C9   mov         dword ptr [ebp-1Ch],eax

004012CC   cmp         dword ptr [ebp-1Ch],0

004012D0   je          process+91h (004012e1)

004012D2   push        1

004012D4   mov         ecx,dword ptr [ebp-1Ch]

004012D7   call        @ILT+0(desk::`scalar deleting destructor') (00401005)

004012DC   mov         dword ptr [ebp-28h],eax

004012DF   jmp         process+98h (004012e8)

004012E1   mov         dword ptr [ebp-28h],0

74:   }

    上面是一段普通的new、delete使用代码。但是我们发现,简单的一个语句,在汇编器看来,却需要做这么多的内容,这是为什么呢,我们不妨来自习看一看:

 

    72句:汇编中有两个函数调用,一个是new调用,也就是我们重定义的new函数,一个是构造函数,最后的几行代码主要是把构造函数返回指针赋值给一些临时变量,可忽略

 

    73句:汇编中首先让指针和0进行了判断,然后调用了一个函数,似乎没有调用我们的delete函数,我们可以跟进去看一下:

 

 

desk::`scalar deleting destructor': 

00401410   push        ebp 

00401411   mov         ebp,esp 

00401413   sub         esp,44h 

00401416   push        ebx 

00401417   push        esi 

00401418   push        edi 

00401419   push        ecx 

0040141A   lea         edi,[ebp-44h] 

0040141D   mov         ecx,11h 

00401422   mov         eax,0CCCCCCCCh 

00401427   rep stos    dword ptr [edi] 

00401429   pop         ecx 

0040142A   mov         dword ptr [ebp-4],ecx 

0040142D   mov         ecx,dword ptr [ebp-4] 

00401430   call        @ILT+75(desk::~desk) (00401050) 

00401435   mov         eax,dword ptr [ebp+8] 

00401438   and         eax,1 

0040143B   test        eax,eax 

0040143D   je          desk::`scalar deleting destructor'+3Bh (0040144b) 

0040143F   mov         ecx,dword ptr [ebp-4] 

00401442   push        ecx 

00401443   call        @ILT+80(desk::operator delete) (00401055) 

00401448   add         esp,4 

0040144B   mov         eax,dword ptr [ebp-4] 

0040144E   pop         edi 

0040144F   pop         esi 

00401450   pop         ebx 

00401451   add         esp,44h 

00401454   cmp         ebp,esp 

00401456   call        __chkesp (00408810) 

0040145B   mov         esp,ebp 

0040145D   pop         ebp 

0040145E   ret         4 

desk::`scalar deleting destructor':

00401410   push        ebp

00401411   mov         ebp,esp

00401413   sub         esp,44h

00401416   push        ebx

00401417   push        esi

00401418   push        edi

00401419   push        ecx

0040141A   lea         edi,[ebp-44h]

0040141D   mov         ecx,11h

00401422   mov         eax,0CCCCCCCCh

00401427   rep stos    dword ptr [edi]

00401429   pop         ecx

0040142A   mov         dword ptr [ebp-4],ecx

0040142D   mov         ecx,dword ptr [ebp-4]

00401430   call        @ILT+75(desk::~desk) (00401050)

00401435   mov         eax,dword ptr [ebp+8]

00401438   and         eax,1

0040143B   test        eax,eax

0040143D   je          desk::`scalar deleting destructor'+3Bh (0040144b)

0040143F   mov         ecx,dword ptr [ebp-4]

00401442   push        ecx

00401443   call        @ILT+80(desk::operator delete) (00401055)

00401448   add         esp,4

0040144B   mov         eax,dword ptr [ebp-4]

0040144E   pop         edi

0040144F   pop         esi

00401450   pop         ebx

00401451   add         esp,44h

00401454   cmp         ebp,esp

00401456   call        __chkesp (00408810)

0040145B   mov         esp,ebp

0040145D   pop         ebp

0040145E   ret         4

    上面的代码便是跟到0x401005之后遇到的代码,这里有一个跳转,真正函数开始的地方是0x401410。这里我们发现函数实际上还是调用了我们定义的delete函数和desk的析构函数。只不过析构函数一定要放在delete调用之前。所以,这里我们就看到了,c++中new的真正含义就是先分配内存,然后调用构造函数;而delete则是先对变量进行析构处理,然后free内存,这就是new和delete的全部意义。掌握了这个基础,可以帮助我们本地对内存进行很好的管理。

 

    (3)friend算术符重载和普通算术符重载的区别

 

    有一种算术符的重载是这样的:

 

 

class desk 

    int price; 

public: 

    desk(int value):price(value) {} 

    ~desk() {} 

    friend desk operator+ (desk& d1, desk& d2); 

}; 

 

desk operator +(desk& d1, desk& d2) 

    desk d(0); 

    d.price = d1.price + d2.price; 

    return d; 

 

void process() 

    desk d1(3); 

    desk d2(4); 

    desk d = d1 + d2; 

    return; 

class desk

{

       int price;

public:

       desk(int value):price(value) {}

       ~desk() {}

       friend desk operator+ (desk& d1, desk& d2);

};

 

desk operator +(desk& d1, desk& d2)

{

       desk d(0);

       d.price = d1.price + d2.price;

       return d;

}

 

void process()

{

       desk d1(3);

       desk d2(4);

       desk d = d1 + d2;

       return;

}

    感兴趣的同学可以汇编看一下,找一找它和普通的非友元函数有哪些区别。不过上面的代码还是让我们看出了一些端倪:

 

    a)友元函数不属于类,因为定义的时候我们发现没有desk::这样的前缀

 

    b)友元算术符重载需要比普通的算术符重载多一个输入参数

 

    c)友元函数在进行算术重载定义的时候需要多定义一个临时变量d,这在函数operator+()可以看出来

 

    d)友元算术重载函数会破坏原来类地封装性

 

    e)友元函数实际上就是全局函数

 

 

 

 

算术运算符使用的经验总结:

 

    (1)算术重载函数是一把双刃剑,务必小心使用

 

    (2)内部算术符函数优先使用于非友元函数

 

    (3)遇到= 号重载特别注意一下指针

 

    (4)重载的时候函数的内容要和重载的运算符一致,不用重载的是+,实际运算的是相减的内容

 

    (5)除非特别需要重载,负责别重载

 

    (6)重载的时候多复用已经存在的重载运算符

 

    (7)new、delete除了内存管理和测试,一般不重载,全局new、delete严谨重载

 

    (8)相关运算符重载要在stl中使用,务必注意返回值