如果编译器内联了一个使用静态而不是动态绑定调用的成员函数,它可能能够优化掉this指针。举个简单的例子:
#include <iostream>
using std::cout;
using std::endl;
class example {
public:
int foo() const { return x; }
int foo(const int i) { return (x = i); }
private:
int x;
};
int main(void)
{
example e;
e.foo(10);
cout << e.foo() << endl;
}
带有标志的 GCC 7.3.0-march=x86-64 -O -S能够编译cout << e.foo()为三个指令:
movl $10, %esi
leaq _ZSt4cout(%rip), %rdi
call _ZNSolsEi@PLT
这是对std::ostream::operator<<. 请记住,这cout << e.foo();是std::ostream::operator<< (cout, e.foo());. 并且operator<<(int)可以写成两种方式:static operator<< (ostream&, int)作为非成员函数,左侧的操作数是显式参数,或者operator<<(int),作为成员函数,它是隐式的this。
编译器能够推断出e.foo()将始终是常量10。由于 64 位 x86 调用约定是在寄存器中传递函数参数,因此编译为单movl条指令,该指令将第二个函数参数设置为10. 该leaq指令将第一个参数(可能是显式ostream&或隐式this)设置为&cout. 然后程序call对该函数进行a。
但是,在更复杂的情况下(例如,如果您有一个将 anexample&作为参数的函数),编译器需要查找this,因为this它告诉程序它正在使用哪个实例,因此x要查找哪个实例的数据成员。
考虑这个例子:
class example {
public:
int foo() const { return x; }
int foo(const int i) { return (x = i); }
private:
int x;
};
int bar( const example& e )
{
return e.foo();
}
该函数bar()被编译为一些样板文件和指令:
movl (%rdi), %eax
ret
您还记得在前面的示例中%rdi,x86-64 上是第一个函数参数,this即调用的隐式指针e.foo(). 将其放在括号中(%rdi),表示在该位置查找变量。(由于example实例中的唯一数据是x,&e.x恰好与本例中的相同&e。)将内容移动到%eax设置返回值。
在这种情况下,编译器需要隐式this参数foo(/* example* this */)才能找到&e,因此&e.x. 事实上,在成员函数内部(不是static)x,this->x和(*this).x都意味着同样的事情。