在c++中,调用对象的非静态成员函数过程中,编译器会自动添加this指针作为第一个参数。我们从汇编层面看看this指针是如何传进来的。
使用如下简单的程序来演示:
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汇编如下:
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验证偏移量:
(gdb) p &(((A*)0)->b)
$1 = (int *) 0x190
(gdb)
可以看到, 编译器确实将this指针作为第一个参数传到非静态成员函数中了。
看了成员函数的汇编, 再看看main函数中是如何调用的。
main函数的汇编如下:
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申请的对象,可能会有所不同。










