目录
1. 本文目的:
当你写完一个类时,有没有好奇它会被编译器翻译成啥哪?你那看似富丽堂皇的房子,其实在装修前都是一堆灰突突的水泥而已,在C++的世界里编译器就是那个技术高超的装修工,他这搭一管子那扯一杆线使得房子住起来很舒服,本文就是想看看这些管子和线在C++的世界里是如何部署的。
2. 注意:
本文例子都是在Linux上64位环境下使用g++编译,Windows上与之不同。
编译&运行:g++ SingleVirtualClass.cpp -std=c++11; ./a.out
3. 开始探索:
3.1 最简单的例子走起:直接从带虚函数的类开始。
#include<iostream>
using namespace std;
class Base{
public:
int i;
int j;
virtual void print(){cout<<"I am print. addr of i= "<<hex<<&i<<endl;}
Base(int _i, int _j):i(_i),j(_j){}
};
int main(){
Base base(1,2);
cout<<"offset of i: "<<(char*)&(base.i)-(char*)&base<<endl; //1. offset of i: 8
cout<<"offset of j: "<<(char*)&(base.j)-(char*)&base<<endl; //2. offset of j: 12
}
可以看到 i相对base的偏移是8,j是12,那么前8个字节是什么东东?其实是vptr(指向虚函数表的指针)。
3.2 虚函数表
为了实现C++的多态,C++使用了一种动态绑定的技术。这个技术的核心是虚函数表。
如果类有虚函数,则每个实例化的对象除了存储成员变量外还在偏移为0处存储一个指针指向虚函数表,虚函数表中存储的是每个虚函数的地址。让我们扩充上面的例子通过虚函数表间接调用虚函数。
/*zhaimin.cn@gmail.com*/
#include<iostream>
#include<typeinfo>
using namespace std;
class Base{
public:
int i;
int j;
virtual void print(){cout<<"I am print. addr of i= "<<hex<<&i<<endl;}
Base(int _i, int _j):i(_i),j(_j){}
};
int main(){
Base base(1,2);
cout<<"offset of i: "<<(char*)&(base.i)-(char*)&base<<endl; //offset of i: 8
cout<<"offset of j: "<<(char*)&(base.j)-(char*)&base<<endl; //offset of j: 12
void** vtable = *(void***)&base; //取出前8个字节的值
typedef void (*Func)(void*);
Func f1 = (Func)vtable[0];//vtable[0]就是函数print的地址
f1(&base); //I am print. addr of i= ...
3.3 虚函数的排列顺序
如果有多个虚函数,按声明顺序排列。
让我们添加三个虚函数,并依次调用虚函数表中的每一项,看输出是否与声明顺序一致。
/*zhaimin.cn@gmail.com*/
#include<iostream>
#include<typeinfo>
using namespace std;
/*********************************************
&base --> |vptr| --> |print | --> def of print
|i | |print2 | --> def of print2
|j | |print3 | --> def of print3
***********************************************/
class Base{
public:
int i;
int j;
private:
virtual void print(){cout<<"I am print. addr of i= "<<hex<<&i<<endl;}
public:
virtual void print2(){cout<<"I am print2"<<endl;}
virtual void print3(){cout<<"I am print3. i="<<i<<endl;}
Base(int _i, int _j):i(_i),j(_j){}
};
int main(){
Base base(1,2);
cout<<"offset of i: "<<(char*)&(base.i)-(char*)&base<<endl; //offset of i: 8
cout<<"offset of j: "<<(char*)&(base.j)-(char*)&base<<endl; //offset of j: 12
void** vtable = *(void***)&base;
typedef void (*Func)(void*);
Func f1 = (Func)vtable[0];
f1(&base); //I am print. addr of i=
Func f2 = (Func)vtable[1];
f2(0); //I am print2
Func f3 = (Func)vtable[2];
f3(&base); //I am print3. i=1
}
上面的输出不但说明虚函数表是按声明顺序排列各个虚函数的地址,而且:
如果你注意到把print()声明为private后依然可以间接调用它,这说明了C++所谓的private、public、protected权限只是在编译阶段验证的,运行时并不验证。
3.4 vtable[-1]指向type_info
如果你看过C++对象模型相关的资料就会知道虚函数表前面还有一项,它指向type_info. 让我们继续通过扩充上面的例子来核实这个结论。
/*zhaimin.cn@gmail.com*/
#include<iostream>
#include<typeinfo>
using namespace std;
/*********************************************
&base --> |vptr| --> |print | --> def of print
|i | |print2 | --> def of print2
|j | |print3 | --> def of print3
***********************************************/
class Base{
public:
int i;
int j;
private:
virtual void print(){cout<<"I am print. addr of i= "<<hex<<&i<<endl;}
public:
virtual void print2(){cout<<"I am print2"<<endl;}
virtual void print3(){cout<<"I am print3. i="<<i<<endl;}
Base(int _i, int _j):i(_i),j(_j){}
};
int main(){
Base base(1,2);
cout<<"offset of i: "<<(char*)&(base.i)-(char*)&base<<endl; //offset of i: 8
cout<<"offset of j: "<<(char*)&(base.j)-(char*)&base<<endl; //offset of j: 12
void** vtable = *(void***)&base;
typedef void (*Func)(void*);
Func f1 = (Func)vtable[0];
f1(&base); //I am print. addr of i=
Func f2 = (Func)vtable[1];
f2(0); //I am print2
Func f3 = (Func)vtable[2];
f3(&base); //I am print3. i=1
Base b1(1,2);
Base b2(3,4);
void** vtable1 = *(void***)&b1;
type_info* ti1 = (type_info*)vtable1[-1];
void** vtable2 = *(void***)&b2;
type_info* ti2 = (type_info*)vtable2[-1];
cout<<"addr of type_info for b1: "<<hex<<ti1<<endl; //They points to the same type_info instance.
cout<<"addr of type_info for b2: "<<hex<<ti2<<endl;
cout<<typeid(b1).name()<<endl;
}
最后三行的输出是一样的,不但说明vtable[-1]确实指向type_info实例,而且打印的地址相同说明了每个类在全局就一个描述这个类的type_info实例。
4. 结论
/*********************************************
|type_info*
&base --> |vptr| --> |print | --> def of print
|i | |print2 | --> def of print2
|j | |print3 | --> def of print3
***********************************************/
本节只探讨有虚函数但无继承情况下的C++对象模型,下一节看看有继承时又是如何布局的哪?