前言
从前也对this指针感到疑惑,比如,它为什么能够用来表示当前对象。
后来了解了一些底层原理后,就逐渐想通了。
定义
从语法上来讲,this指针可以看做是当前对象,我们可以用它来间接访问对象里的成员和函数。
从本质上来说,this指针是一个特殊类型的指针,它保存了对象的地址。
如果你不懂什么是指针的话,可以看一下博主写的这篇文章 理解指针
原理
为了方便说明,使用如下代码来解释this指针的原理。
#include<stdio.h>
class TestThis
{
public:
TestThis()
{
}
};
int main()
{
TestThis _this;
return 0;
}
在Windows XP 上用VC6.0 编译后,打开IDA。如下所示。
.text:0040D3F0 ; int __cdecl main(int argc, const char **argv, const char **envp)
.text:0040D3F0 _main proc near ; CODE XREF: _main_0j
.text:0040D3F0
.text:0040D3F0 var_44 = byte ptr -44h
.text:0040D3F0 var_4 = byte ptr -4
.text:0040D3F0 argc = dword ptr 8
.text:0040D3F0 argv = dword ptr 0Ch
.text:0040D3F0 envp = dword ptr 10h
.text:0040D3F0
.text:0040D3F0 push ebp
.text:0040D3F1 mov ebp, esp
.text:0040D3F3 sub esp, 44h
.text:0040D3F6 push ebx
.text:0040D3F7 push esi
.text:0040D3F8 push edi
.text:0040D3F9 lea edi, [ebp+var_44]
.text:0040D3FC mov ecx, 11h
.text:0040D401 mov eax, 0CCCCCCCCh
.text:0040D406 rep stosd
.text:0040D408 lea ecx, [ebp+var_4] ; this
.text:0040D40B call j_??0TestThis@@QAE@XZ ; TestThis::TestThis(void)
.text:0040D410 xor eax, eax
.text:0040D412 pop edi
.text:0040D413 pop esi
.text:0040D414 pop ebx
.text:0040D415 add esp, 44h
.text:0040D418 cmp ebp, esp
.text:0040D41A call __chkesp
.text:0040D41F mov esp, ebp
.text:0040D421 pop ebp
.text:0040D422 retn
.text:0040D422 _main endp
.text:0040D422
.text:0040D422 ; -----------------------------------------------
观察下面这两行
.text:0040D408 lea ecx, [ebp+var_4] ; this
.text:0040D40B call j_??0TestThis@@QAE@XZ ; TestThis::TestThis(void)
可以看到编译器把 ebp-4 (即对象的首地址)送入 ecx 中,以此作为 参数 来调用构造函数。
而 this指针保存的是对象的首地址。
由此可知,ecx 被当成this指针 用来初始化构造函数。
继续查看构造函数,
.text:0040D430 ; public: __thiscall TestThis::TestThis(void)
.text:0040D430 ??0TestThis@@QAE@XZ proc near ; CODE XREF: TestThis::TestThis(void)j
.text:0040D430
.text:0040D430 var_44 = byte ptr -44h
.text:0040D430 var_4 = dword ptr -4
.text:0040D430
.text:0040D430 push ebp
.text:0040D431 mov ebp, esp
.text:0040D433 sub esp, 44h
.text:0040D436 push ebx
.text:0040D437 push esi
.text:0040D438 push edi
.text:0040D439 push ecx
.text:0040D43A lea edi, [ebp+var_44]
.text:0040D43D mov ecx, 11h
.text:0040D442 mov eax, 0CCCCCCCCh
.text:0040D447 rep stosd
.text:0040D449 pop ecx
.text:0040D44A mov [ebp+var_4], ecx
.text:0040D44D mov eax, [ebp+var_4]
.text:0040D450 pop edi
.text:0040D451 pop esi
.text:0040D452 pop ebx
.text:0040D453 mov esp, ebp
.text:0040D455 pop ebp
.text:0040D456 retn
.text:0040D456 ??0TestThis@@QAE@XZ endp
.text:0040D456
首先push ecx是为了防止寄存器冲突,因为初始化堆栈也要用到ecx。
虽然在这里this指针并没有做什么事情,但是你可以看到this被保存到 ebp-4 (局部变量)中,并且作为函数的第一个参数。
保存this指针是为了以后能对成员和函数作访问,因为this指针保存了对象首地址,所以只要加上偏移量就能得到对象里的成员位置。
使用ecx寄存器来传递参数这种方式被称为 thiscall,是WIndows平台特有的传参方式。
理解
既然this指针是一个特殊类型的指针,那么它也符合指针的特性,可以使用指针的操作。
我们是否可以用this指针来打印构造函数的返回地址呢?
当然是可以的。
看如下代码。
#include<stdio.h>
class TestThis
{
public:
TestThis()
{
printf("The return address of TestThis is: 0x%p \n",(int *)(this+1)+1);
}
double a;
float b;
int c;
char d;
};
int main()
{
TestThis _this;
printf("The size of _this is: 0x%x\n",sizeof(_this));
getchar();
return 0;
}
运行结果如图。
部分堆栈中数据如下
0012FF68 |CCCCCCCC // a 占8个字节,也是this指针指向的地址
0012FF6C |CCCCCCCC
0012FF70 |CCCCCCCC // b
0012FF74 |CCCCCCCC // c
0012FF78 |CCCCCCCC // d
0012FF7C |CCCCCCCC //字节对齐
0012FF80 ]0012FFC0 // 上个函数的ebp
0012FF84 |00401609 返回到 test.<模块入口点>+0E9 来自 test.00401005 // 返回地址
this指针的大小是对象的大小即24个字节,this+1 可以直接把this指针移动到尾部。
因为保存的地址大小是4个字节,所以this可以转成int指针,再加一得到返回地址。