C++虚表的有关问题
C++虚表的问题
下午研究了一下虚表的机制,在CodeBlocks上试验了一下,有两个问题,如下
#include <iostream>
using namespace std;
class Base1{
int iBase1;
public:
Base1(int i=0):iBase1(i){}
virtual ~Base1(){
cout << "Base1 destoried\n";
}
virtual void print(){
cout << "Base1::print\n";
}
virtual void f(){
cout << "Base1::f\n";
}
};
class Base2{
int iBase2;
public:
Base2(int i=1):iBase2(i){}
virtual ~Base2(){
cout << "Base2 destoried\n";
}
virtual void print(){
cout << "Base2::print\n";
}
virtual void g(){
cout << "Base2::g\n";
}
};
class Derived:public Base1,public Base2{
int iDerived;
public:
Derived(int i=2):iDerived(i){}
virtual ~Derived(){
cout << "Derived destoried\n";
}
virtual void print(){
cout << "Derived::print\n";
}
virtual void m(){
cout << "Derived::m\n";
}
};
int main(){
Derived d(100);
typedef void (*Func)(void);
Func f;
//f=(Func)*((int*)*(int*)&d+1); //这是第一个虚表中的第一个函数,应该存放的派生类的析构函数,此处若调用f,则会引起程序崩溃
//f();
f=(Func)*((int*)*(int*)&d+2); //这里应该是第一个虚表中第二个函数,为派生类中的print函数(派生类重写基类虚函数)
f();
f=(Func)*((int*)*(int*)&d+3); //这里应该是第一个虚表中的第三个函数,应该是Base1中的f函数
f();
f=(Func)*((int*)*(int*)&d+4); //这里应该是第一个虚表中的第四个函数,此处应该是派生类中的m函数
f();
f=(Func)*((int*)*((int*)&d+2)+1); //这里是第二个虚表中的第一个函数,实验的结果应该是派生类的析构函数
f();
f=(Func)*((int*)*((int*)&d+2)+2); //这里应该是派生类的print函数(派生类重写基类虚函数)
f();
f=(Func)*((int*)*((int*)&d+2)+3); //这里应该是Base2中的g函数
f();
return 0;
}
问题一: 如上面注释所说,我进入第一个虚表的第一个函数处执行,也就是执行派生类的析构函数,编译器会报错,我觉得原因是该对象已经被释放,因此下面的执行会访问到已经释放的空间,所以程序会崩溃
但是,我进入第二个虚表的第一个函数处执行,同样也是派生类的析构函数,编译器编译通过且顺利执行,我有点儿迷惑这是为什么?
问题二:我在有些博客上看到,在虚表的第一项存放的是对象的RTTI信息,我在Codeblocks上实验确实是这样,我想问的是,第一项存放的到底是怎么信息?是指向type_info对象的指针么?
另外,在单继承情况下,派生类对象的虚表的第一项是派生类的运行时类型识别信息,那么在多继承情况下,这里假设是两个基类,在派生类的对象的中,有两个虚表,这两个虚表的第一项分别是两个基类的运行时类型识别信息,那么派生类的运行时类型识别信息怎么识别?
------解决思路----------------------
1. 成员函数大小和 int 不一样大,但虚函数表中的 slot 是和 int 一样大的。
2. 成员函数指针不能转成一般函数来调用,但是虚函数表中的函数地址是可以当成普通函数来调用的,只要把参数处理好。
3. 对于没有访问成员变量的函数来说,如果直接调用函数(即没有通过虚函数表来调用函数),有没有 this 指针是无所谓的。
4. 不能因为各个编译器实现不一样就放弃探索, 正如你发的那张表一样,每个编译器实现都不一样,还是有人去把它探索出来了,而且在这个基础上实现了高效的委托,可见这些探索还是很有意义的。
------解决思路----------------------
1.虚函数表中的 slot 和 int 一样大的? 不应该是和size_t一样大么.
2. 是的,但是微软x86的__thiscall是this指针是靠寄存器传递的,纯靠c++没法完成.
3. 同上,x86上因为调用约定产生崩溃而发帖询问的人有不少.
4. 没说没有意义,自己主要就是用COM组件,基本就违背了"猜测虚表实现",只是对光用一段代码就
说虚函数怎么这么样的说:这样不好,要考虑完整
下午研究了一下虚表的机制,在CodeBlocks上试验了一下,有两个问题,如下
#include <iostream>
using namespace std;
class Base1{
int iBase1;
public:
Base1(int i=0):iBase1(i){}
virtual ~Base1(){
cout << "Base1 destoried\n";
}
virtual void print(){
cout << "Base1::print\n";
}
virtual void f(){
cout << "Base1::f\n";
}
};
class Base2{
int iBase2;
public:
Base2(int i=1):iBase2(i){}
virtual ~Base2(){
cout << "Base2 destoried\n";
}
virtual void print(){
cout << "Base2::print\n";
}
virtual void g(){
cout << "Base2::g\n";
}
};
class Derived:public Base1,public Base2{
int iDerived;
public:
Derived(int i=2):iDerived(i){}
virtual ~Derived(){
cout << "Derived destoried\n";
}
virtual void print(){
cout << "Derived::print\n";
}
virtual void m(){
cout << "Derived::m\n";
}
};
int main(){
Derived d(100);
typedef void (*Func)(void);
Func f;
//f=(Func)*((int*)*(int*)&d+1); //这是第一个虚表中的第一个函数,应该存放的派生类的析构函数,此处若调用f,则会引起程序崩溃
//f();
f=(Func)*((int*)*(int*)&d+2); //这里应该是第一个虚表中第二个函数,为派生类中的print函数(派生类重写基类虚函数)
f();
f=(Func)*((int*)*(int*)&d+3); //这里应该是第一个虚表中的第三个函数,应该是Base1中的f函数
f();
f=(Func)*((int*)*(int*)&d+4); //这里应该是第一个虚表中的第四个函数,此处应该是派生类中的m函数
f();
f=(Func)*((int*)*((int*)&d+2)+1); //这里是第二个虚表中的第一个函数,实验的结果应该是派生类的析构函数
f();
f=(Func)*((int*)*((int*)&d+2)+2); //这里应该是派生类的print函数(派生类重写基类虚函数)
f();
f=(Func)*((int*)*((int*)&d+2)+3); //这里应该是Base2中的g函数
f();
return 0;
}
问题一: 如上面注释所说,我进入第一个虚表的第一个函数处执行,也就是执行派生类的析构函数,编译器会报错,我觉得原因是该对象已经被释放,因此下面的执行会访问到已经释放的空间,所以程序会崩溃
但是,我进入第二个虚表的第一个函数处执行,同样也是派生类的析构函数,编译器编译通过且顺利执行,我有点儿迷惑这是为什么?
问题二:我在有些博客上看到,在虚表的第一项存放的是对象的RTTI信息,我在Codeblocks上实验确实是这样,我想问的是,第一项存放的到底是怎么信息?是指向type_info对象的指针么?
另外,在单继承情况下,派生类对象的虚表的第一项是派生类的运行时类型识别信息,那么在多继承情况下,这里假设是两个基类,在派生类的对象的中,有两个虚表,这两个虚表的第一项分别是两个基类的运行时类型识别信息,那么派生类的运行时类型识别信息怎么识别?
------解决思路----------------------
1. 成员函数大小和 int 不一样大,但虚函数表中的 slot 是和 int 一样大的。
2. 成员函数指针不能转成一般函数来调用,但是虚函数表中的函数地址是可以当成普通函数来调用的,只要把参数处理好。
3. 对于没有访问成员变量的函数来说,如果直接调用函数(即没有通过虚函数表来调用函数),有没有 this 指针是无所谓的。
4. 不能因为各个编译器实现不一样就放弃探索, 正如你发的那张表一样,每个编译器实现都不一样,还是有人去把它探索出来了,而且在这个基础上实现了高效的委托,可见这些探索还是很有意义的。
------解决思路----------------------
1.虚函数表中的 slot 和 int 一样大的? 不应该是和size_t一样大么.
2. 是的,但是微软x86的__thiscall是this指针是靠寄存器传递的,纯靠c++没法完成.
3. 同上,x86上因为调用约定产生崩溃而发帖询问的人有不少.
4. 没说没有意义,自己主要就是用COM组件,基本就违背了"猜测虚表实现",只是对光用一段代码就
说虚函数怎么这么样的说:这样不好,要考虑完整