如果编译器内联了一个使用静态而不是动态绑定调用的成员函数,它可能能够优化掉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;
}
带有-march=x86-64 -O -S 标志的GCC 7.3.0 能够将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。
不过,在更复杂的情况下——例如,如果你有一个函数以 example& 作为参数——编译器需要查找 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
您还记得在前面的示例中,x86-64 上的 %rdi 是第一个函数参数,隐含的 this 指针用于调用 e.foo()。将其放在括号中,(%rdi),表示在该位置查找变量。 (由于example 实例中的唯一数据是x,因此在这种情况下&e.x 恰好与&e 相同。)将内容移至%eax 设置返回值。
在这种情况下,编译器需要 foo(/* example* this */) 的隐式 this 参数才能找到 &e 并因此找到 &e.x。事实上,在成员函数内部(不是static),x、this->x 和 (*this).x 都是同一个意思。