2015年9月10日早上10点,面试京东数字营销业务部机器学习系统架构工程师,二面挂在了C++编译机制上面,当然自己平时没有了解过这方面的东西,挂了也算是理所当然。

网上这方面的资料不多,但是有一本翻译过来的文档,
C++ Under The Hood
这本书里面详细的介绍了C++的编译机制,有兴趣的可以在这里看中译本

  • 面试官首先问我最近都看什么技术方面的书?
    答:平常看C++的书也比较多,就说C++pimer,effective C++什么的

  • 问了三次握手我详细介绍了一下,然后问:现在假如一个Client向Server发送数据,前提是已经建立连接了,然后Server忽然down掉了,然后过了一会儿又重启了,会发生什么?
    答:这里要问的因该是RST这个标志位,在Server死了的一瞬间会给Client发送一个RST = 1的请求,然后就回断开连接,这样后面的所有的数据都不会接收到,然后连接也断开了。

  • select、poll、epoll分别是干什么的?
    答:(当时没有回答上来,前一天看了忘了也是悲催)有待详细的学习。

  • 说那么现在我给你两个类,然后又第三个类继承前两个类,然后这个类在内存中是怎么分配的?
    然后其实自己也没有搞清楚面试官问的点,后面看了这个文档之后发现他要问的其实是C++的编译原理,后面给我了很多提示,但是我依然没有往正确的方向上走。

在VC++编译器中,类在内存中是这样布局的
首先排列非虚继承的基类实例(按照变量定义的顺序依次分配,当然中间可能有需要内存对其之类的情况)
有虚基类时,为每个基类增加一个隐藏的vbptr(Virtual base class pointer),除非已经从非虚继承的类继承过来了。
排列派生类的新数据成员(依然按照定义的先后)
最后排列每个虚基类的实例(编译器优化)

1.没有虚继承

现在对于这样一个类

struct C 
{    
    int c1;
    void cf(); 
};
struct E 
{    
    int e1;
    void ef(); 
};
struct F : C,E
{    
    int f1;
    void ff(); 
};

其内存分配如下
从左往右依次每个类中变量按顺序分配
从左往右依次每个类中变量按顺序分配,现在我们要进行如下访问。

F f;
f->c1 //直接内存开始到c1所占内存的长度
f->e1 //开始+C的偏移量到e1所占长度
f->f1 //C+E的偏移量和到f1所占长度

这样成员变量的访问还是很迅速的。因为内嵌的基类实例地址比起派生类,要么地址相同(F和C),要么相差固定偏移量(F和E)。

2.含有虚继承

为了保证多个类继承一个相同的基类,而这个类不重复的分配内存,那么就有了虚继承。
假如这样

struct Employee { ... };
struct Manager : virtual Employee { ... }; 
struct Worker : virtual Employee { ... }; 
struct MiddleManager : Manager, Worker { ... }; 

这样就需要维护一个vbptr,其中保存的是每个每个类访问虚函数的偏移量,so…不管是实现开销还是调用开销都比较大,因为每次都要访问这个vbptr。

3.成员变量访问以及强制转换

其实前面都说了,这个保存起来之后,没有虚继承的话每次只要是才开始或者一个固定的偏移量去找,有虚继承,不同的类访问虚类变量的时候要根据变化的vbptr计算一次。
其实强制转话也是一样的,向下强制转化可以看成是把内存中的前面一部分或者后面一部分忽略掉,向上强制转化就是加一部分,其实和访问成员变量的开销是一样的。

成员函数

每个成员函数有一个隐藏的变量,this指针,在后台初始化为指向成员函数所在的对象,所以在函数中成员变量的访问就是通过后台计算this指针的偏移来进行
首先需要申明一点的是,成员函数是不用存在内存中的,我们知道编译的时候分程序段和数据段,前面说的那些事存在数据段的(栈上),而成员函数之类的要保存在程序段。
这里的难点主要是涉及到虚函数,虚函数的访问需要一个调整块之类的东西,后面在详细看看之后再写。

Logo

开源、云原生的融合云平台

更多推荐