如果编译器内联了一个使用静态而不是动态绑定调用的成员函数,它可能能够优化掉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
都意味着同样的事情。