现在的位置: 首页 > 综合 > 正文

this指针探秘

2014年07月02日 ⁄ 综合 ⁄ 共 2262字 ⁄ 字号 评论关闭

bj在它的著作(C++语言的设计与演化 p62)里说在早期的C++里
this是一个可以被赋值的东东,只是在继承在堆栈里很难处理,后来才被淘汰掉
class X{
//...
public:
   X();
//...
};

X::X(){
    this=my_alloc(sizeof(X));
    //...
}

X x;//为x分配内存

既然可以为它赋值那么就意味着this可能是真实存在的这么一个变量

之后我又机会读到了深度探索C++对象模型,书中对this的描述是,this是一个函数参数
 
float manitude3d(const Point3d *_this){...};
float Point3d::manitude3d()const{...}
这两种方式是等价的,编译器在内部将后者转化为前者,因此
obj.magintude();变成了maginitude_7Point3dFv(&obj);

看完相关的章节,觉得自己对于C++是有了一定的理解,知道this为什么只能
在成员函数中使用,它是一个函数参数,函数参数能在定义它的函数外使用吗?当然不能

也许到这里理解的可能就够了,但是偏偏我这个人有点拧,我就想既然this是真实的变量,那我为什么不能取它地址?给它赋值?又过了一段时间,我开始学习汇编,用的是清华的《汇编语言》作者王爽,这真的是一本不错的书,通俗易懂,拿来入门真是合适不过了,以前我用的是冶金工业出版社的《80X86汇编语言程序设计》讲的我就是晕的一塌糊涂,由此我也想到了,一本好书的定义,应该是能让人一看就茅舍顿开,而一本垃圾书则相反,通过一段时间的学习后(这段时间我待业,刚好有足够的时间)我分别跟踪了VC6.0和BCB6.0中的成员函数执行过程,终于觉得是很久以来想不通的东西都明白了,很爽

先说VC6.0
当程序执行
x.foo();
lea ecx,dword ptr[ebp-4]        //对象地址存储到ecx中
call @ILT +10(X::foo)(0040100f) //跳转到0040100f处
0040100f jmp X::foo             //0040100f处的jmp跳转的函数真正的地址

void X::foo(){
//...
mov doword ptr[ebp-4],ecx
//将对象地址从ecx中读出,写入到foo的ebp-4处
//...
}

由此可见,在VC中对象地址是通过寄存器变量传递进成员函数,但是进入函数后做完一些初始化的工作以后
把对象地址写入到foo的ebp-4处,这个ebp-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

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

再说bcb6.0,当程序执行
x.foo();
lea eax,[ebp-0x04]
push eax          //将对象地址压栈

call X::foo()     //调用X::foo()

在bcb6.0中

#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 (*pfoo) (A*) ;

int main(){

A a;
int *vptr=(int*)&a;            /[v]ptr指向虚指针
int *vtbl=(int*)*vptr;         /[v]tbl指向虚表格
pfoo=(void (*)(A*))*vtbl;      //指向虚表格第一个单元
(*pfoo)(&a);                   //引用这个单元
vtbl++;                        //指向虚表格下一单元
pfoo=(void (*)(A*))*vtbl;      //指向虚表格下一单元
(*pfoo)(&a);                   //引用下一单元

return 0;
}

总结一下:
this指针在概念上可以理解为函数的参数,这个参数是隐藏的
x.foo();这样一个调用的时候被转换成为foo(&x),当然这一切是编译器做的,
对于用户是不可见的

再讲不同编译器的区别
1.VC里foo的原型是 void X::foo(X * const register this)
2.BCB里foo的圆形是 void X::foo(X * const this)

再解释为什么this有一些很神奇的特征
1.为什么没有定义?   //编译器定义了
2.为什么不能赋值?   //this为const指针
3.为什么不能取地址? //编译器的限制,如果使用汇编,完全可以突破编译器的限制

参考http://bbs.csdn.net/topics/100069525

【上篇】
【下篇】

抱歉!评论已关闭.