this指针探秘

深度探索C++对象模型对this的描述是,this是一个函数参数

 

float   manitude3d(const   Point3d   *_this){...} 
float   Point3d::manitude3d()const{...}

这两种方式是等价的,编译器在内部将后者转化为前者,因此 
obj.magintude();
变成了maginitude_7Point3dFv(&obj);

 

如何理解编译器在内部将后者转化为前者这一句话呢?我们知道世界上第一个C++编译器是Cfront,Bjarne Stroustrup,Lippman等人开发的,那在当时最流行的编程语言是C,在各种机器各种操作系统下都有C的编译器版本,这样C的程序在当时是最易移植的,因此Bjarne Stroustrup决定Cfront生成等价的C代码以获得可移植性。就是说Cfront读入你编写的.cpp文件经过词法分析等编译过程,结果没有生成.obj文件却生成了一个.c文件,然后再用C编译器对.c文件进行编译链接,最终生成的程序是一个彻头彻尾的C程序(所以Cfront也被称为预处理器)

 

这样,编译器在看到成员函数的定义时会直接修改源代码:修改函数名称,在函数的参数里添加这么一个名叫“this”的指针定义,对成员函数的调用也采取类似的方式,在调用语句的参数列表偷偷加上对象的地址,最后保存为.c文件,C编译器进行最终的编译链接,生成可执行程序,可以看到经过编译器编译之后,C++的成员函数其实也就是一个普通的函数,thiselement pop(sqstack *s)中的s也没有什么本质上的区别,就是一个指向数据结构的指针而已,今天的C++编译器已经不再是C预处理器,但是依旧沿用这样的思想

 

Ok,说到这里大家对什么是this指针已经有个感性的理解了吧

第一,this指针为什么没有定义就能直接使用,答案是编译器定义了

第二,this指针是一个函数参数,所以它的使用范围仅在成员函数内部

第三,this指针为什么指向对象?调用时偷偷把对象地址给它传递过去了

 

下面我们将进入汇编层次去看一看成员函数的调用以及this指针到底是如何传递的,恩,需要我们有一点汇编知识,以及通过内嵌的汇编来测试我们的想法

 


x.foo();                                 //VC6.0的反汇编代码
lea   ecx,dword   ptr[ebp-4]             //
对象地址存储到ecx 
call   @ILT   +10(X::foo)(0040100f)   //
转到0040100f 
0040100f   jmp   X::foo               // jmp
跳转的函数真正的地址 

void   X::foo(){ 
//... 
mov   dword   ptr[ebp-4],ecx 
//
将对象地址从ecx中读出,写入到fooebp-4 
this->a=2;

mov   eax,dword ptr [ebp-4] //通过this指针为对象赋值时从ebp-4读取对象地址

mov   dword ptr [eax],2 // eax = &x; *(int*)(eax+0)= 2

//... 
}

 

由此可见,在VC Debug中对象地址是通过寄存器变量(ecx)传递进成员函数,进入函数后做完一些初始化的工作以后,把对象地址写入到fooebp-4处,程序如果要读取对象属性都需要从ebp-4中读出对象地址,了解VC成员函数的调用过程,我们可以大胆做一些测试程序

 

void   X::foo(X *p){ 
        __asm{ 
            mov  eax,doword   ptr[ebp+8] 
            mov  dword   ptr[ebp-4],eax 
            //this = p 
      } 
      this- >a=10;//p- >a=10; 
}//
偷梁换柱,this实际已经指向*p ,这时通过this对对象的修改都是针对*p

void   X::foo(){ 
        X   **pTHIS; 
        __asm{ 
            lea    eax,dword   ptr[ebp-4] 
            mov   dword   ptr   [ebp-8],eax 
            //pTHIS=&this 
      } 
      (*pTHIS)- >a=10;//this- >a=10;       
}//pTHIS
指向了this因此**pTHIS==*this

 

刚才我们是基于VC Debug下的一个讨论,现在讨论下release下的成员函数调用,在release下,对象地址不再写入ebp-4处,从而使this指针变成一个纯粹的寄存器变量,由于release的汇编代码非常难读,我们只写一个测试程序证明我们的观点:)


void X::foo(){

      X *THIS;

     __asm{

         mov  dword ptr [ebp-8],ecx

     //THIS = this

      }

      THIS->a=2;

}

 

因此在this指针在VC中其实是一个彻底的寄存器的变量,VC的成员函数其实是void  X::foo  ( const  register  X  *this)

 

OK,上面说了那么多就是在证明一点:this指针只是一个普通的指针,被编译器定义的一个函数参数,现在,我们要证明另一点,C++的成员函数被编译之后只是普通的函数,由于VC++this指针是寄存器变量,不能很清楚的显示这个关系,我们选用Borland C++ Builder 6.0 作为我们测试的工具,BCB依靠栈来传递对象地址

 

x.foo(); 
lea   eax,[ebp-0x04] 
push   eax               //
将对象地址压栈 
call   X::foo()           //
调用X::foo()

 

先介绍一点C++对象模型的知识,我们知道C++的类定义包含虚函数,那么这个类产生的对象都会包含一个虚指针,虚指针会指向一个名叫虚表格的函数指针数组,数组存储的都是虚函数的地址,C++依靠这些来实现多态,而通常虚指针又位于对象的前端(我们很幸运,BCB正是这样)

 

既然函数存在与内存中,那么我们可以依靠虚指针和虚表格的指向关系一路找到虚函数,我们再定义一个函数指针指向虚函数,通过函数指针调用虚函数(并把对象地址传递进去)

 

#pragma   hdrstop

#include   <iostream.h >

class   A{

public:

        virtual   void   foo(){

                this- >x=3;

                cout < < "this- >x=   " < <this- >x < <endl;

        }

        virtual   void   bar(){

                this- >x=4;

                cout < < "this- >x=   " < <this- >x < <endl;

        }

        int   x;

};

void   (*pvFunc)   (A*)   ;

 

int   main(){

 

A   a;

int   *vptr=(int*)&a;                  //vptr指向虚指针

int   *vtbl=(int*)*vptr;                //vtbl指向虚表格

pvFunc =(void (*)(A*))*vtbl;          //指向虚表格第一个单元(foo函数)

(*pvFunc)(&a);                        //通过函数指针执行foo函数

vtbl++;                                //vptr指向虚表格下一单元

pvFunc =(void (*)(A*))*vtbl;          //指向虚表格下一单元(bar函数)

(*pvFunc)(&a);                        //通过函数指针执行bar函数

return   0;

}

 

(*pvFunc)(&a); 你可以看到,经过一些非常规的方法我们居然就可以这样调用虚函数,而且我们确实把对象地址给送入到函数里,可以证实有那么一个参数存在,可以证实成员函数就是一个普通函数。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值