关于this指针的思考
this指针的引入
我们都知道,在面向对象的设计中,类的成员方法可以随意访问同样存储方式的成员变量。
比如:
class Test
{
int na;
public:
void fun()
{
na = 4; // 访问成员变量
printf("%d\n",na);
return;
}
};
在Test类中,fun函数可以访问na,那么在使用该方法的时候是如何找到成员变量的地址的呢?
this指针的汇编解释
Test * pTT = new Test();
pTT->fun();
我们按照程序员的习俗先来看看反汇编
pTT->fun();
00E3237F mov ecx,dword ptr [pTT]
00E32382 call Test::fun (0E311E5h)
可以看到,先把当前对象的地址放在ecx寄存器中,然后进入fun函数入口表。
fun中的反汇编内容如下:
00E329D0 push ebp // 栈底指针压栈
00E329D1 mov ebp,esp //把栈顶作为新的栈底
00E329D3 sub esp,0CCh //开辟出0CCh个字节单元,作为程序的交换数据区,及204个字节
00E329D9 push ebx // 压栈ebx
00E329DA push esi // 压栈esi
00E329DB push edi // 压栈edi
00E329DC push ecx // 压栈ecx
00E329DD lea edi,[ebp-0CCh] //把ebp-0cch的值赋给edi,即最开始申请到的204个字节的首地址
00E329E3 mov ecx,33h //作为rep的循环次数, 33h = 51; 51 * 4 = 204
00E329E8 mov eax,0CCCCCCCCh // 初始化内容
00E329ED rep stos dword ptr es:[edi] //把204个字节用cc初始化,执行完后edi前进到栈底的位置,edi = ebx
00E329EF pop ecx // 取出ecx
00E329F0 mov dword ptr [this],ecx // 原始的反汇编是mov [edi-8],ecx,也就是说把edi-8处的内存空间作为this,保存的对象首地址,或者说是ebp-8为首地址
16: na = 4;
00E329F3 mov eax,dword ptr [this] // 对象的首地址用[this]表示
00E329F6 mov dword ptr [eax],4 // 通过[eax+n]的方式取出对象中的每一个元素。
总结
在用对象的指针调用对象的方法时,会把该对象的首地址保存在ecx寄存器中,然后在程序执行时,先在栈中开辟一段内存空间(大小根据具体情况而定),然后把[ebp-8]作为this 指针变量。
扩展
在面向对象中,我们说方法和属性是一个整体,他们一起描述一个对象。所以,在方法中理所当然的可以随意使用自己的属性。但是在考虑如何用编译器实现这样语法时,就有许多不同的方法了。this 指针只是对实现这一功能的一种笼统描述。上面的例子是C++中this 指针的实现方法,我们可以考虑一下如果不用这种方法,还可以如何实现this 指针?
比如说,1、把对象的指针作为函数的第一个参数,在调用函数的时候先进行压栈,然后在函数里面使用,函数调用完成后,由调用者释放空间。
2、在函数的第一行添加一个this指针定义,同样在调用前把对象的指针保存在ecx中,然后执行函数的时候把ecx赋值给this,使用完成后由被调用者来释放。
当然我们在使用的时候,可以统一把他们作为函数的局部变量来使用,在VS 编译器中就是这样做的。