您的位置:首页 > 编程语言 > C语言/C++

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

2017-09-25 18:01 239 查看

一个示例

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

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 {
public:
void test()const {

}
};


这个后置
const
的标识符我们肯定经常会使用,但是想必没有过多的深究,我们一般都会把这个当作一个简单的给编译器看的标识符,但是其实这个也可以用
const
进行解释:

这个后置
const
是用来修饰
this
指针的,所以在编译期间,在这个函数作用范围中,对非静态成员的改变都是不被允许的,因为
this
指针指向的空间是不能被修改的
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: