对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指针指向的空间是不能被修改的
相关文章推荐
- c++中基类与派生类中隐含的this指针的分析
- C++下this指针实现机制分析(针对vc++ 6.0)
- c++主项练习错题分析(1) const,this指针
- C++-this指针
- C++ 类中的this指针
- c++指针经典题目分析
- C++中的this指针
- 【c++】this指针的使用
- c++对象数组和this指针
- C++每日一练(this指针、函数模板)
- C++中关于this指针的理解
- C++调用成员函数需要this指针的情况
- C++中this指针的用法详解
- [置顶] C++智能指针的分析与使用
- 浅析C++中的this指针
- Visual C++及C++中的智能指针应用分析
- C++ this 指针详解
- C++中this指针的用法详解
- [c++]this指针理解
- C++中this指针的用法详解