对C++中的this指针的分析

来源:互联网 发布:淘宝天猫怎么申请 编辑:程序博客网 时间:2024/05/18 01:46

一个示例

首先让我们观察如下代码:

namespace ClassTest {    class A {    private:        int m_int1;        int m_int;        static int st_int;    public:        void test1() { cout << "test1" << endl; }        void test2() { cout << "test2" << endl; }        static void test3() { cout << "test3 " << st_int << endl; }        void test4() { m_int = 5; }    };    int A::st_int = 5;    void test() {        A* nullP = NULL;        nullP->test1();        nullP->test2();        nullP->test3();        nullP->test4();    }}int main() {    ClassTest::test();    system("pause");    return 0;}

你认为这些代码都能成功执行吗?
想必你肯定会奇怪我居然会问这种问题,一个已经指向了NULL的类指针,怎么可能还能成功调用成员函数呢?
但是假如你对C++的类的实现机制有比较多的了解,就会思考出上述的代码执行情况可能会是这样的:

A* nullP = NULL;nullP->test1();//执行nullP->test2();//执行nullP->test3();//执行nullP->test4();//出错,因为传入的this指针为NULL,但是却想访问非静态成员变量

why?

思考

因为在C++中,类的成员函数的执行并不只是直接跳转到函数体然后就直接进行执行了,而是会在调用成员函数之前,传入一个this指针(比如上面的代码,传入的this指针的类型为A* const,其值为NULL )。
所以我们可以很容易的想到,当我们使用一个类指针去执行其对应的成员函数的时候,编译器也许会帮我们做下面的事情:

  • 根据指针类型找到这个成员函数
  • this放在一个固定寄存器中传入然后在所有参数压栈后再进行压栈
  • 执行成员函数的代码,当使用到非静态成员变量的时候在其前面加上this->

    所以上面的test4函数可能会被编译器添添改改变成下面这种样子:

void test4( A* const this){      this->m_int = 5;};

实践验证,深入剖析

我们可以通过VS生成的汇编代码看看我说的对不对(通过VS的单步调试和反汇编我们可以很容易的做到)

执行以上的代码,我们可以发现在执行test4函数之前,会先执行如下汇编代码:

0133C5BA  mov         ecx,dword ptr [nullP]  0133C5BD  call        ClassTest::A::test4 (013175C2h)  

不难看出,在成员函数调用之前,nullP的值被放在了ecx寄存器中,然后接着跟踪,test4内部的汇编代码如下:

    void test4(){ 0133BC20  push        ebp  0133BC21  mov         ebp,esp  0133BC23  sub         esp,0CCh  0133BC29  push        ebx  0133BC2A  push        esi  0133BC2B  push        edi  0133BC2C  push        ecx  0133BC2D  lea         edi,[ebp-0CCh]  0133BC33  mov         ecx,33h  0133BC38  mov         eax,0CCCCCCCCh  0133BC3D  rep stos    dword ptr es:[edi]  0133BC3F  pop         ecx  0133BC40  mov         dword ptr [this],ecx              m_int = 5;0133BC43  mov         eax,dword ptr [this]  0133BC46  mov         dword ptr [eax+4],5          };

ecx最后被压栈

注意下面这几行汇编代码:

00F0BC3F  pop         ecx  00F0BC40  mov         dword ptr [this],ecx              m_int = 5;0133BC43  mov         eax,dword ptr [this]  0133BC46  mov         dword ptr [eax+4],5  

我们可以看到在访问m_int的时候,编译器先将 ecx出栈,然后将ecx的值放在this指针应该在的位置(这里我不是太清楚,但是我想的是vs便编译器会将this指针放在堆栈上的固定位置),然后将this的值放在eax寄存器上,然后加上偏移值就可以访问到其成员变量,如果我们将test4的函数改成如下形式:

void test4(){      m_int1=5;}

然后汇编代码变成了这样:

000CBC40  mov         dword ptr [this],ecx              m_int1 = 5;000CBC43  mov         eax,dword ptr [this]  000CBC46  mov         dword ptr [eax],5  

我们可以推断,第一个非静态成员变量就放在this指针指向的位置(在没有析构函数的时候),当我们需要访问其余非静态成员变量时,就加上由其变量类型主导的偏移量。

我们再观察一下上面所有成员函数执行之前的汇编代码:

        nullP->test1();000CC5A5  mov         ecx,dword ptr [nullP]  000CC5A8  call        ClassTest::A::test1 (0A75BDh)          nullP->test2();000CC5AD  mov         ecx,dword ptr [nullP]  000CC5B0  call        ClassTest::A::test2 (0A75CCh)          nullP->test3();000CC5B5  call        ClassTest::A::test3 (0A75C7h)          nullP->test4();000CC5BA  mov         ecx,dword ptr [nullP]  000CC5BD  call        ClassTest::A::test4 (0A75C2h)  

可以发现,我上面说的那些想法都是对的,在执行一个非静态成员函数之前,this指针就会被传入,在访问成员变量的时候,this指针会被使用,所以前三个函数不会出错,因为成员变量没被访问,this指针就算为NULL,也不会出错,因为this指针不会被使用。
我们还可以发现test3函数执行之前并没有传入this指针,为什么?
很简单,我就不说了,留给自己思考。

this指针总结

this指针何时被创建?
在函数调用之前,实际上,成员函数默认第一个参数就为T* const this,不同的编译器实现方法有所不同。

this指针何时被销毁?
在函数执行完成之后

this指针何时不会被当作参数传入?
全局函数,静态函数都不会使用this指针。

思考以下如下代码:

    class B {    publicvoid test()const {        }    };

这个后置const的标识符我们肯定经常会使用,但是想必没有过多的深究,我们一般都会把这个当作一个简单的给编译器看的标识符,但是其实这个也可以用const进行解释:
这个后置const是用来修饰this指针的,所以在编译期间,在这个函数作用范围中,对非静态成员的改变都是不被允许的,因为this指针指向的空间是不能被修改的

原创粉丝点击