码迷,mamicode.com
首页 > 其他好文 > 详细

自己的真实面试

时间:2018-09-05 17:59:59      阅读:189      评论:0      收藏:0      [点我收藏+]

标签:操作符   处理   otl   ati   for   强制   过程   概念   new   

视源面试总结

  1. 虚函数列表的结构(vptr) -- RTTI (Run-time type information 运行时刻识别)
    • 虚表(virtual table)和虚函数表, 存放着该类所有的虚函数对应的函数指针; 所以虚函数是占用内存空间的
    • 虚函数的实现的基本原理
    • 深入虚表结构
    • 派生类虚函数的构造过程: 虚函数替换过程发生在编译时
      • 拷贝基类的虚函数表(vptr)
      • 替换已重写虚函数指针
      • 追加子类自己的虚函数指针
    • 通过基类的函数访问派生类或者基类的成员函数时, 这个指针先指向类对象的首地址, 这个首地址其实是虚函数表(里面放着该类所有的虚函数), 如果是基类自然而然的找到并调用基类的虚函数, 如果是指向的是派生类, 那就调用在编译时就生成好的虚函数表中对应的虚函数;
    • B::bar和D::bar在各自虚函数表中的偏移位置是相等的。
    • 无论pb指向哪种类型的对象,只要能够确定被调函数在虚函数中的偏移值
    • 待运行时,能够确定具体类型,并能找到相应vptr了,就能找出真正应该调用的函数
    • 虚函数指针中的ptr部分为虚函数表中的偏移值(以字节为单位)加1
    • 多重继承中, 基类指针指向的位置可能不是派生类对象的起始位置
  2. 虚函数的实现过程(virtual)
    • 虚拟函数的最大特点,是通过父函数的指针可以调用子函数的方法来实现多态。
    • 是如何实现的呢?通过父指针要确定子类的的类型,由于可能有多个子类,而且是运行时刻识别,这样就需要子类的对象本身包含自身的信息。
    • 虚表除了包含虚函数指针,还包含其它一些信息(如:RTTI信息、偏移值等)
  3. new 和 malloc的区别
    • new是从自由存储区上分配内存, malloc是从堆上分配内存
    • new/delete会调用构造/析构函数对对象进行初始化和销毁
    • operator new/delete可以进行重载
    • 自由存储区和堆的区别(free store VS heap)
    • 将得很好的参考链接
      • C++中内存分为5个区, 分别是堆、栈、自由存储区、全局/静态变量存储区和常量存储区
      • 划分自由存储区和堆的界线就是new/delete与malloc/free
      • 很多编译器的new/delete都是以malloc/free为基础来实现的
      • 堆是操作系统维护的一块特殊内存
      • 自由存储区时new/delete动态分配和释放对象的抽象概念, 通过new来申请的内存区域可以成为自由存储区
      • 堆是操作系统维护的一块内存块, 而自由存储区时C++中通过new与delete动态分配和释放对象的抽象概念。堆与自由存储区并不等价, 因为new可以通过运算符重载, 改用其他内存来实现自由存储区.
      • 自由存储区是否能够等同于堆, 等价于new是否能在堆上动态的分配内存, 这取决于operator new的具体实现细节; 自由存储区不仅可以是堆, 还可以是静态存取区, 这都是看operator new 是在哪里对对象进行分配内存的。 new甚至可以不为对象分配内存;
    1. 申请内存所在位置:
      • new操作符从自由存储区(free store)上为对象动态分配内存空间, 而malloc函数从堆上动态分配内存
    2. 返回类型安全性:
      • new操作符内存分配成功时, 返回的是对象类型的指针, 类型严格与对象匹配, 无需进行类型转换, 所以new是符合类型安全性的操作符。
      • 而malloc内存分配成功则是返回void*, 需要通过强制类型转换将void*指针转换成我们需要的类型
      • 类型安全很大程度上可以等价于内存安全, 类型安全的代码不会试图访问自己没有被授权的内存区域。
    3. 内存分配失败时的返回值:
      • new内存分配失败时, 会抛出bac_alloc异常, 它不会返回NULL指针; malloc分配失败时会返回NULLl; new根本不会返回NULL, 失败早就抛出异常了。
    4. 是否需要指定内存大小:
      • new操作符申请内存分配时无须指定内存块的大小, 编译器会根据类型信息自行计算, 而malloc则需要显式地指出所需内存的尺寸。
    5. 是否调用构造函数/析构函数:
      • 使用new操作符来分配对象内存时会经过三个步骤:
        • 调用operator new函数(对于数组是 operator new[])分配足够大的, 原始的, 未命名的内存空间以便存储特定类型的对象;
        • 编译器运行响应相应的构造函数以构造对象, 并为其传入初值;
        • 对象构造完成后, 返回一个指向该对象的指针.
      • 使用delete操作符来释放对象会经历两个步骤:
        • 调用对象的析构函数;
        • 编译器调用operator delete(或 operator delete[])操作符重载函数来释放内存空间.
      • new/delete会调用对象的构造/析构函数, 而malloc则不会.
      • 用malloc/free来处理C++的自定义类型是不合适的, 标准库中凡是需要构造/析构的类型通通不适合.
    6. 对数组的处理:
      • C++提供了new[]与delete[]来专门处理数组类型;
      • 使用new[]分配的内存必须使用delete[]进行释放;
      • new对数组的支持体现在它会分别调用构造函数初始化每一个数组元素, 释放对象时为每个对象调用析构函数;
      • 注意delete[]要与new[]配套使用, 不然会出现数组对象部分释放的现象, 造成内存泄漏;
      • 对于malloc, 它并不知道你在这块内存上要放的是数组还是别的东西, 反正它就给你一块原始的内存, 在给你一块内存地址就完事了;
      • malloc动态分配一个数组的内存, 还需要我们手动指定数组的大小;
    7. new与malloc是否可以相互调用:
      • 操作符new/delete的实现可以基于malloc, 而malloc的实现不可以去调用new
    8. 是否可以被重载:
      • 操作符new/delete可以被重载, 标准库定义了new与delete的8个重载版本:
      //这些版本可能抛出异常
      void * operator new(size_t);
      void * operator new[](size_t);
      void * operator delete (void * );
      void * operator delete[](void *0;
      //这些版本承诺不抛出异常
      void * operator new(size_t ,nothrow_t&) noexcept;
      void * operator new[](size_t, nothrow_t& ) noexcept;
      void * operator delete (void *,nothrow_t& ) noexcept;
      void * operator delete[](void *0,nothrow_t& )noexcept;
      • 可以自定义这8个函数版本中的任意一个, 前提是自定义版本必须位于全局作用域或者类作用域中;
      • 我们有足够的自由去重载operator new/delete, 以决定我们的new与delete如何为对象分配内存, 如何回收对象;
      • 而malloc/free是并不允许重载的.
    9. 能够直观地重新分配内存:
      • 使用malloc分配内存后, 如果使用过程中发现内存不足, 可以使用realloc函数进行内存重新分配实现内存的扩充;
      • realloc先判断当前的指针所指向内存是否有足够的空间, 如果有, 原地扩大可分配的内存地址, 并且返回原来的地址指针;
      • 如果空间不够, 先按照新指定的大小分配空间, 将原有数据从头到尾拷贝到新分配的内存区域, 然后释放原来的内存区域.
      • new没有这样直观的配套设施来扩充内存.
    10. 客户处理内存分配不足;
      • 在operator new抛出异常以反映一个未获得满足的需求之前, 它会先调用一个用户指定的错误处理函数, 这就是new_handler;
      • new_handler是一个指针类型, 指向一个没有参数没有返回值的函数, 即错误处理函数;
      • 客户需要调用set_new_handler, set_new_handler的参数为new_handler指针, 指向operator new无法分配足够内存时该调用的函数;
      • 其返回值也是个指针, 指向set_new_handler被调用前正在执行(但马上就要发生替换)的那个new_handler函数.
      • 对于malloc, 客户并不能够去编程决定内存不足以分配时要干什么事情, 只能看着malloc返回NULL.
  4. python的数据结构: 组元, 列表, 字典
  5. 函数的调用过程
    • 函数调用过程及函数栈帧分析
    • 函数帧分析参考链接
    • 知乎上面的答案
    • 具体过程:
      1. 关于栈: 栈是向下生长的, 所谓向下生长是指从内存高地址->低地址的路径延伸;
        • 栈有栈顶和栈低, 那么栈顶的地址要比栈底地址低;
        • 对x86体系的CPU而言, 寄存器ebp(base pointer)可称为"帧指针"或"基址指针", 寄存器esp(stack pointer), 可称为"栈指针"
        • ebp(externed base pointer)-扩展的基址指针, 在未受改变之前始终指向栈帧的开始, 也就是栈底, 所以ebp的用途是在堆栈中寻址使用的;
        • esp是会随着数据的入栈和出栈移动的, 也就是说, esp始终指向栈顶.
      2. 假设函数A调用函数B, 我们称A函数为调用者, B函数为被调用者, 则函数调用过程可以这么描述:
        • 先将调用者(A)的堆栈的基址(ebp)入栈, 以保存之前任务的信息
        • 然后将调用者(A)的栈顶指针(esp)的值赋给ebp, 作为新的基址(即被调用者B的栈底)
        • 然后这个基址(被调用者B的栈底)上开辟(一般用sub指令)相应的空间用作被调用者B的栈空间
        • 函数B返回后, 从当前栈帧的ebp即恢复着A的栈顶(esp), 使栈顶恢复函数B被调用前的位置;
        • 然后调用者A再从恢复后的栈顶可弹出之前的ebp值(可以这么做是因为这个值在函数调用前一步被压入堆栈)
        • 这样ebp和esp就都恢复了调用函数B前的位置, 也就是栈恢复函数B调用前的状态.
        leave
        ret 
        相当于:
        mov %ebp, %esp
        pop %ebp
      3. 由于栈对齐对策缘故, 编译器分配栈空间时可能会有没用到的内存地址.
    • n内存中的栈主要用于保存函数参数, 返回值, 返回地址, 本地变量等; 一切的函数调用都要将不同的数据、地址压入或者弹出栈.
    • 栈帧(stack frame), 其本质其实就是一种栈, 只是这种栈专门用于保存函数调用过程的各种信息(参数, 返回地址, 本地变量).
    • 栈帧有栈顶和栈底之分, 其中栈顶的地址最低, 栈底的地址最高, SP(栈指针)就是一直指向栈顶的;
    • %ebp指向栈底, 也就是基址指针; 用%esp指向栈顶, 也就是栈指针;
    • 调用函数的参数按照从右到左的顺序压入栈中
    • 将返回地址压入栈中, 压入的栈应该是调用者的栈帧

这次面试的总结: 我的基础知识还是一点都不牢固, 平时缺少知识点的总结

比特大陆面试

  1. 直接问项目,主要问了可穿戴项目, 实习项目
    • 痛点就是Linux环境编程, 说道没有项目
    • 可穿戴项目问到了, 内核的调度机制, 优先级选址, 读写分离设计的优缺点, 操作系统移植, bootload(问得都不是很细)
    • 问了关于功耗问题, 熊大, 问了性能问题(都答得不太好, 这次面试给自己打60分吧), 希望能够给我一个机会

自己的真实面试

标签:操作符   处理   otl   ati   for   强制   过程   概念   new   

原文地址:https://www.cnblogs.com/longjiang-uestc/p/9592219.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!