看了好多的博客,其中很多对我帮助很大.这么我也写一回博客,讲讲的的心得。C++的书看了很多,C++反汇编很值得学习,不是为了真的去做反汇编,而是去更深入理解C++那些复杂语法背后发生的事情。关于C++虚函数,在定义了虚函数的类中,需要构造函数来初始化虚函数指针。将虚表地址赋值于类的头部。具体看代码;
#include <iostream>
using namespace std;
struct A
{
A(int a)
{
x = a;
prin();
}
virtual void prin()
{
cout << "x";
}
virtual ~A()
{
}
int x;
};
int main()
{
A a(1);
return 0;
}
在A的对象被创建时,B的构造函数中发生了很多事,下面一一道来。
7 A(int a)
0x00420140 push %ebp
0x00420141 mov %esp,%ebp
0x00420143 sub $0x18,%esp
0x00420146 mov %ecx,-0xc(%ebp)
8 {
0x00420149 mov -0xc(%ebp),%eax
0x0042014C movl $0x4735b8,(%eax)
9 x = a;
0x00420152 mov -0xc(%ebp),%eax
0x00420155 mov 0x8(%ebp),%edx
0x00420158 mov %edx,0x4(%eax)
10 prin();
0x0042015B mov -0xc(%ebp),%eax
0x0042015E mov %eax,%ecx
0x00420160 call 0x420120 <A::prin()>
11 }
0x00420165 leave
0x00420166 ret $0x4
7 A(int a)
0x00420140 push %ebp
0x00420141 mov %esp,%ebp
0x00420143 sub $0x18,%esp
0x00420146 mov %ecx,-0xc(%ebp)
8 {
0x00420149 mov -0xc(%ebp),%eax
0x0042014C movl $0x4735b8,(%eax)
9 x = a;
0x00420152 mov -0xc(%ebp),%eax
0x00420155 mov 0x8(%ebp),%edx
0x00420158 mov %edx,0x4(%eax)
10 prin();
0x0042015B mov -0xc(%ebp),%eax
0x0042015E mov %eax,%ecx
0x00420160 call 0x420120 <A::prin()>
11 }
0x00420165 leave
0x00420166 ret $0x4
以上为A构造函数中汇编代码,加粗加红的部分即为初始化虚表操作。所谓的类,就是一个struct,类中函数与类的关联在于编译器为每个类中函数加了一个参数,为struct对象的首地址,仅此而已。
mov %ecx,-0xc(%ebp)
其中的ecx寄存器中的值即为类对象a的首地址。由于有虚函数,需要类对象第一个void *指针类型大小的空间存放虚表指针,所以将ecx值保存,在取出值赋值给eax,$0x4735b8即为虚表首地址。来看看它所在内存空间的值
0x4735b8: 20 01 42 00 8c 01 42 00|6c 01 42 00 00 00 00 00
小端模式,32位编址位数。值为0x420120,该值即为虚函数print的地址。下面加粗加蓝的语句及调用该地址。有虚函数的类需要有构造函数,没有编译器会提供来初始化虚表指针。虚表指针空间之后即为类成员空间。开始处ecx的值加4就是int a的地址了。
今天就写到这里了。以后再写继承下虚函数机制的各种隐藏操作。