在c++
中,调用对象的非静态成员函数过程中,编译器会自动添加this
指针作为第一个参数。我们从汇编层面看看this
指针是如何传进来的。
使用如下简单的程序来演示:
cpp
class A
{
private:
int a[100];
int b;
public:
void SetB(int b)
{ this->b = b;}
int GetB() { return this->b;}
};
int main()
{
A oa ;
oa.SetB(0x888);
int b = oa.GetB();
return 0;
}
编译后反汇编A::SetB
汇编如下:
shell
0000000000400536 <_ZN1A4SetBEi>:
400536: 55 push %rbp
400537: 48 89 e5 mov %rsp,%rbp
40053a: 48 89 7d f8 mov %rdi,-0x8(%rbp)
40053e: 89 75 f4 mov %esi,-0xc(%rbp)
400541: 48 8b 45 f8 mov -0x8(%rbp),%rax
400545: 8b 55 f4 mov -0xc(%rbp),%edx
400548: 89 90 90 01 00 00 mov %edx,0x190(%rax)
40054e: 5d pop %rbp
40054f: c3 retq
可以看到,寄存器rdi
和esi
分别表示第一个参数this
和第二个参数int b
。
最后通过mov %edx,0x190(%rax)
指令实现this->b = b;
。
类内变量b
相对于对象起始地址this
的偏移是0x190
,可以通过gdb
验证偏移量:
shell
(gdb) p &(((A*)0)->b)
$1 = (int *) 0x190
(gdb)
可以看到, 编译器确实将this
指针作为第一个参数传到非静态成员函数中了。
看了成员函数的汇编, 再看看main
函数中是如何调用的。
main
函数的汇编如下:
shell
00000000004004fd <main>:
4004fd: 55 push %rbp
4004fe: 48 89 e5 mov %rsp,%rbp
400501: 48 81 ec a0 01 00 00 sub $0x1a0,%rsp
400508: 48 8d 85 60 fe ff ff lea -0x1a0(%rbp),%rax
40050f: be 88 08 00 00 mov $0x888,%esi
400514: 48 89 c7 mov %rax,%rdi
400517: e8 1a 00 00 00 callq 400536 <_ZN1A4SetBEi>
40051c: 48 8d 85 60 fe ff ff lea -0x1a0(%rbp),%rax
400523: 48 89 c7 mov %rax,%rdi
400526: e8 25 00 00 00 callq 400550 <_ZN1A4GetBEv>
40052b: 89 45 fc mov %eax,-0x4(%rbp)
40052e: b8 00 00 00 00 mov $0x0,%eax
400533: c9 leaveq
400534: c3 retq
400535: 90 nop
main
函数中定义了局部变量oa
,在栈上申请内存。
第三条指令sub $0x1a0,%rsp
, 表示在栈上申请了0x1a0
个字节,此时%rsp=-0x1a0(%rbp)
。-0x1a0(%rbp)
其实就是栈顶,也是对象oa
的地址,即this
指针。
第四条指令lea -0x1a0(%rbp),%rax
执行后,this
指针保存在%rax
中。
第6/7条指令,分别将this
指针及变量int b(0X888)
放入rdi
及rsi
表示第一/二个参数, 然后调用A::SetB
上述程序,对象在栈上。 如果对象在堆上的情况,即通过new
申请的对象,可能会有所不同。