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

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

 

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

 

 

 

 

    在算术符重载里面,“=”重载可能是最经常使用的一种。但是好多人就误以为在函数中,凡是类出现“=”的地方,那就是调用算术符重载,其实不然。为什么呢?我们可以看看下面的代码。首先,我们定义一个基本类:

 

 

class data 

    char* value; 

    int number; 

public: 

    explicit data(int num = 0){ 

        if(num){ 

            number = num; 

            value = (char*)malloc(num); 

        } 

    } 

 

    data(const data& d){ 

        number = d.get_number(); 

        value = (char*)malloc(d.get_number()); 

        memmove(value, d.get_point(), d.get_number()); 

    } 

 

    ~data(){ 

        if(number) 

            free(value); 

    } 

 

    data& operator=(const data& d){ 

        if(number) 

            free(value); 

        number = d.get_number(); 

        value = (char*)malloc(d.get_number()); 

        memmove(value, d.get_point(), d.get_number()); 

        return *this; 

     

    } 

 

    int get_number() const {return number;} 

    char* get_point() const {return value;} 

}; 

class data

{

       char* value;

       int number;

public:

       explicit data(int num = 0){

              if(num){

                     number = num;

                     value = (char*)malloc(num);

              }

       }

 

       data(const data& d){

              number = d.get_number();

              value = (char*)malloc(d.get_number());

              memmove(value, d.get_point(), d.get_number());

       }

 

       ~data(){

              if(number)

                     free(value);

       }

 

       data& operator=(const data& d){

              if(number)

                     free(value);

              number = d.get_number();

              value = (char*)malloc(d.get_number());

              memmove(value, d.get_point(), d.get_number());

              return *this;

      

       }

 

       int get_number() const {return number;}

       char* get_point() const {return value;}

};

    定义好了函数之后,我们就开始对这个类进行调用,同样代码如下所示:

 

45:       data m(10); 

0040108D   push        0Ah 

0040108F   lea         ecx,[ebp-14h] 

00401092   call        @ILT+30(data::data) (00401023) 

00401097   mov         dword ptr [ebp-4],0 

46:       data p = m; 

0040109E   lea         eax,[ebp-14h] 

004010A1   push        eax 

004010A2   lea         ecx,[ebp-1Ch] 

004010A5   call        @ILT+35(data::data) (00401028) 

004010AA   mov         byte ptr [ebp-4],1 

47:       p = m; 

004010AE   lea         ecx,[ebp-14h] 

004010B1   push        ecx 

004010B2   lea         ecx,[ebp-1Ch] 

004010B5   call        @ILT+5(data::operator=) (0040100a) 

48:   } 

45:       data m(10);

0040108D   push        0Ah

0040108F   lea         ecx,[ebp-14h]

00401092   call        @ILT+30(data::data) (00401023)

00401097   mov         dword ptr [ebp-4],0

46:       data p = m;

0040109E   lea         eax,[ebp-14h]

004010A1   push        eax

004010A2   lea         ecx,[ebp-1Ch]

004010A5   call        @ILT+35(data::data) (00401028)

004010AA   mov         byte ptr [ebp-4],1

47:       p = m;

004010AE   lea         ecx,[ebp-14h]

004010B1   push        ecx

004010B2   lea         ecx,[ebp-1Ch]

004010B5   call        @ILT+5(data::operator=) (0040100a)

48:   }

    上面共有三句话,我们逐一进行分析:

 

    45句:定义了一个临时变量,调用data的构造函数

 

    46句:出现了一个临时变量p,这里发现data类并没有调用算术符重载函数,而是调用了data的构造函数,根据45句所示,调用的肯定不是普通的构造函数,那么剩下的结果只能是拷贝构造函数

 

    47句: 和46句的代码是一致的,但是此时调用的函数才是算术符重载函数

 

    所以说,出现“=”的地方未必调用的都是算术符重载函数,也有可能是拷贝构造函数。那么什么时候是拷贝构造函数,什么时候是算术符重载函数呢?判断的标准其实很简单。如果临时变量是第一次出现,那么调用的只能是拷贝构造函数,反之如果变量已经存在,就像47句一样,那么调用的只能是算术符重载函数,但是我们这里定义的算数符重载函数有一个陷阱,不知道大家看出来没有?

 

    我提示大家一下,这里的算术符重载需不需要判断拷贝的是不是自己呢?

 

void process() 

    data m(10); 

    data p = m; 

    p = p; 

void process()

{

       data m(10);

       data p = m;

       p = p;

}    这里最后一句,如果算术符可以自己拷贝给自己,代码正常编译和运行都没有问题,但是在某些情况下会出现很多意想不到的情况。大家可以跟着我的思路来:

 

data& operator=(const data& d){ 

    if(this == &d)       /* check whether it is self-copy action */ 

        return *this; 

 

    if(number) 

        free(value); 

    number = d.get_number(); 

    value = (char*)malloc(d.get_number()); 

    memmove(value, d.get_point(), d.get_number()); 

    return *this; 

 

       data& operator=(const data& d){

              if(this == &d)       /* check whether it is self-copy action */

                     return *this;

 

              if(number)

                     free(value);

              number = d.get_number();

              value = (char*)malloc(d.get_number());

              memmove(value, d.get_point(), d.get_number());

              return *this;

      

       }

    如果上面的代码没有判断复制的对象是不是自己,那么我们发现实际上value的数据实际上已经free掉了。那么此时重新分配内存,拷贝的数据只有天知道是什么数据。原来value指向的内存空间就存在了很大的不确定性,这就是算术符重载的陷阱。