问题描述:
class myClass { public: void SetNumber(int nNumber) { m_nInt = nNumber; } private: int m_nInt; }; int main(int argc, char* argv[],int _version) { myClass Test; Test.SetNumber(5); return 0; }
上述代码中,SetNumber是如何识别得出来m_nInt是类的成员变量???
分析:
main函数反汇编:
;省略进入main函数的部分汇编代码 push 5 lea ecx, [ebp+var_4] ;此处的[ebp+var_4]指的是Test对象的首地址 call j_myClass__SetNumber ;省略调用Set_Number之后的汇编代码
从上述汇编代码可以看得出来,在main函数调用成员函数SetNumber之前,编译器先将实参5压栈,然后将Test对象的首地址保持到寄存器ecx中,最后直接调用成员SetNumber。从汇编代码中,还可以看出SetNumber的函数名是经过了修改的。
SetNumber函数反汇编:
var_44= dword ptr -44h var_4= dword ptr -4 arg_0= dword ptr 8 ;参数的地址 ;下面是进入函数后,将上一层的调用者的信息保持,压栈。 push ebp mov ebp, esp sub esp, 44h push ebx push esi push edi push ecx lea edi, [ebp+var_44] mov ecx, 11h mov eax, 0CCCCCCCCh rep stosd pop ecx ;恢复ecx,this指针。 mov [ebp+var_4], ecx mov eax, [ebp+var_4] mov ecx, [ebp+arg_0] mov [eax], ecx ;赋值,m_nInt=nNumber pop edi pop esi pop ebx mov esp, ebp pop ebp retn 4 ;被调用者自己恢复栈 myClass__SetNumber endp
从上述的反汇编代码可以看出,在底层成员函数和普通函数是没有区别的。我们所看到的类和对象的概念是编译器提供的,也就是说在调用成员函数的时候,编译器做了一些“小动作”:利用寄存器ecx来保持对象的首地址(即this指针),并以寄存器传参的方式传递给成员函数,这种调用被称为__thiscall。(注意:并不是所有的成员函数调用都是通过ecx来实现的,得看具体的编译器)
__thiscall和__stdcall都是被调用者自己恢复栈。但是两者的区别在于,前者使用到一个寄存器来传递对象的首地址,而非通过栈传递的方式。