oppo面经

用户态与内核态的区别以及区分的缘由

http://www.javashuo.com/article/p-uhfawhjx-cm.htmlhtml

https://blog.csdn.net/qq_39823627/article/details/78736650程序员

  • 内核态与用户态是操做系统的两种运行级别,当程序运行在3级特权级上时,就能够称之为运行在用户态。由于这是最低特权级,是普通的用户进程运行的特权级,大部分用户直接面对的程序都是运行在用户态;
  • 当程序运行在0级特权级上时,就能够称之为运行在内核态。算法

  • 运行在用户态下的程序不能直接访问操做系统内核数据结构和程序。当咱们在系统中执行一个程序时,大部分时间是运行在用户态下的,在其须要操做系统帮助完成某些它没有权力和能力完成的工做时就会切换到内核态(好比操做硬件)。express

处于用户态执行时,进程所能访问的内存空间和对象受到限制,其所处于占有的处理器是可被抢占的数组

处于内核态执行时,则能访问全部的内存空间和对象,且所占有的处理器是不容许被抢占的。缓存

定义和声明的区别

从编译原理上来讲,声明是仅仅告诉编译器,有个某类型的变量会被使用,可是编译器并不会为它分配任何内存。而定义就是分配了内存。安全

int a = 0; //定义并声明了变量 a extern int a; //只是声明了有一个变量 a 存在,具体 a 在哪定义的,须要编译器编译的时候去找。

 如何判断发生内存泄漏

第一:良好的编码习惯,尽可能在涉及内存的程序段,检测出内存泄露。当程式稳定以后,在来检测内存泄露时,无疑增长了排除的困难和复杂度。使用了内存分配的函数,一旦使用完毕,要记得要使用其相应的函数释放掉。数据结构

第二:将分配的内存的指针以链表的形式自行管理,使用完毕以后从链表中删除,程序结束时可检查改链表。函数

第三:Boost 中的smart pointer。性能

为何vector使用2倍扩容

听说不少操做系统会使用伙伴系统(Buddy System)管理内存,因而乎用2^n的数组会不那么容易形成内存碎片。更少的内存碎片 = 更少的内存整理时间。

为何要有大端小端

大小端和CPU有关系,CISC(复杂指令集)CPU通常使用小端数据格式,RISC(精简指令集)通常使用大端数据格式

判断大小端

void judge_bigend_littleend3()
{
    union
    {
        int i;
        char c;
    }un;
    un.i = 1;

    if (un.c == 1)
        printf("小端\n");
    else
        printf("大端\n");
}

  

C++四种强制转换

1) static_cast

用法:static_cast <类型说明符> (变量或表达式)

它主要有以下几种用法:
    (1)用于类层次结构中基类和派生类之间指针或引用的转换
      进行上行转换(把派生类的指针或引用转换成基类表示)是安全的
      进行下行转换(把基类的指针或引用转换为派生类表示),因为没有动态类型检查,因此是不安全的
    (2)用于基本数据类型之间的转换,如把int转换成char。这种转换的安全也要开发人员来保证
    (3)把空指针转换成目标类型的空指针
    (4)把任何类型的表达式转换为void类型
    注意:static_cast不能转换掉expression的const、volitale或者__unaligned属性。

static_cast:能够实现C++中内置基本数据类型之间的相互转换。

若是涉及到类的话,static_cast只能在有相互联系的类型中进行相互转换,不必定包含虚函数

2) const_cast

在C语言中,const限定符一般被用来限定变量,用于表示该变量的值不能被修改。

而const_cast则正是用于强制去掉这种不能被修改的常数特性,但须要特别注意的是const_cast不是用于去除变量的常量性,而是去除指向常数对象的指针或引用的常量性,其去除常量性的对象必须为指针或引用。

用法:const_cast<type_id> (expression)
    该运算符用来修改类型的const或volatile属性。除了const 或volatile修饰以外, type_id和expression的类型是同样的。
    常量指针被转化成很是量指针,而且仍然指向原来的对象;
    常量引用被转换成很是量引用,而且仍然指向原来的对象;常量对象被转换成很是量对象。

常量指针——指向“常量”的指针(const int *p, int const *p)

指针常量——指针类型的常量(int *const p)

3) reinterpret_cast

在C++语言中,reinterpret_cast主要有三种强制转换用途:改变指针或引用的类型将指针或引用转换为一个足够长度的整形将整型转换为指针或引用类型

用法:reinterpret_cast<type_id> (expression)
    type-id必须是一个指针、引用、算术类型、函数指针或者成员指针
    它能够把一个指针转换成一个整数,也能够把一个整数转换成一个指针(先把一个指针转换成一个整数,在把该整数转换成原类型的指针,还能够获得原先的指针值)。
    在使用reinterpret_cast强制转换过程仅仅只是比特位的拷贝,所以在使用过程当中须要特别谨慎

4) dynamic_cast

 用法:dynamic_cast<type_id> (expression)

 (1)其余三种都是编译时完成的,dynamic_cast是运行时处理的运行时要进行类型检查

(2)不能用于内置的基本数据类型的强制转换

(3)dynamic_cast转换若是成功的话返回的是指向类的指针或引用转换失败的话则会返回NULL

(4)使用dynamic_cast进行转换的,基类中必定要有虚函数,不然编译不经过

        B中须要检测有虚函数的缘由:类中存在虚函数,就说明它有想要让基类指针或引用指向派生类对象的状况,此时转换才有意义。

(5)在类的转换时,在类层次间进行上行转换时,dynamic_cast和static_cast的效果是同样的。在进行下行转换时,dynamic_cast具备类型检查的功能,比static_cast更安全。

        向上转换,即为子类指针指向父类指针(通常不会出问题);向下转换,即将父类指针转化子类指针。

       向下转换的成功与否还与将要转换的类型有关,即要转换的指针指向的对象的实际类型与转换之后的对象类型必定要相同,不然转换失败。

        在C++中,编译期的类型转换有可能会在运行时出现错误,特别是涉及到类对象的指针或引用操做时,更容易产生错误。Dynamic_cast操做符则能够在运行期对可能产生问题的类型转换进行测试。

函数调用时栈空间的变化:

https://blog.csdn.net/xi_niuniu/article/details/44978207#commentBox

函数调用大体包括如下几个步骤:

参数入栈:将参数从右向左依次压入系统栈中
返回地址入栈:将当前代码区调用指令的下一条指令地址压入栈中,供函数返回时继续执行
代码区跳转:处理器从当前代码区跳转到被调用函数的入口处
栈帧调整:具体包括
保存当前栈帧状态值,已备后面恢复本栈帧时使用(EBP入栈)
将当前栈帧切换到新栈帧。(将ESP值装入EBP,更新栈帧底部
给新栈帧分配空间。(把ESP减去所需空间的大小,抬高栈顶

 堆和栈的区别:

管理方式:对于栈来说,是由编译器自动管理,无需手动控制;对于堆来讲,分配和释放都是由程序员控制的

空间大小:整体来讲,栈的空间是要小于堆的。通常来说在32位系统下,堆内存能够达到4G的空间,从这个角度来看堆内存几乎是没有什么限制的;可是对于栈来说,通常是有必定的空间大小的

碎片问题:对于堆来说,因为分配和释放是由程序眼控制的(利用new/delete 或 malloc/free),频繁的操做势必会形成内存空间的不连续,从而形成大量的内存碎片,使程序效率下降。对于栈来说,则不会存在这个问题,由于栈是先进后出的数据结构,在某一对象弹出以前,它以前的全部对象都已经弹出。

生长方向:对于堆来说,生长方向是向上的,也就是沿着内存地址增长的方向,对于栈来说,它的生长方式是向下的,也就是沿着内存地址减少的方向增加。

分配方式:堆都是动态分配的,没有静态分配的堆。栈有两种分配方式:静态分配和动态分配,静态分配是编译器完成的,好比局部变量的分配;动态分配由alloca函数进行分配,可是栈的动态分配和堆是不一样的,它的动态分配是由编译器实现的,无需咱们手工实现。

分配效率:栈是机器系统提供的数据结构,计算机会在底层对栈提供支持,分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率很高。堆则是C/C++函数提供的,它的机制是很复杂的,例如为了分配一块内存,库函数会按照必定的算法在堆内存中搜索可用的足够大小的空间,若是没有足够大小的空间(多是因为碎片太多),就有可能调用系统功能去增长程序数据段的内存空间,这样就有机会分到足够大小的内存,而后进行返回。显然,堆的效率要比栈底的多。

请说一下C/C++ 中指针和引用的区别?

1.指针有本身的一块空间,而引用只是一个别名;

2.使用sizeof看一个指针的大小是4,而引用则是被引用对象的大小;

3.指针能够被初始化为NULL,而引用必须被初始化且必须是一个已有对象 的引用;

4.做为参数传递时,指针须要被解引用才能够对对象进行操做,而直接对引 用的修改都会改变引用所指向的对象;

5.能够有const指针,可是没有const引用;

6.指针在使用中能够指向其它对象,可是引用只能是一个对象的引用,不能 被改变;

7.指针能够有多级指针(**p),而引用止于一级;

8.指针和引用使用++运算符的意义不同;

9.若是返回动态内存分配的对象或者内存,必须使用指针,引用可能引发内存泄露。

TCP/IP中如何解决粘包问题?若是一直传输数据怎么拆包?

粘包、拆包发生缘由
发生TCP粘包或拆包有不少缘由,现列出常见的几点,可能不全面,欢迎补充,
一、要发送的数据大于TCP发送缓冲区剩余空间大小,将会发生拆包
二、待发送数据大于MSS(最大报文长度),TCP在传输前将进行拆包
三、要发送的数据小于TCP发送缓冲区的大小,TCP将屡次写入缓冲区的数据一次发送出去,将会发生粘包
四、接收数据端的应用层没有及时读取接收缓冲区中的数据,将发生粘包

粘包、拆包解决办法
经过以上分析,咱们清楚了粘包或拆包发生的缘由,那么如何解决这个问题呢?解决问题的关键在于如何给每一个数据包添加边界信息,经常使用的方法有以下几个:
一、发送端给每一个数据包添加包首部,首部中应该至少包含数据包的长度,这样接收端在接收到数据后,经过读取包首部的长度字段,便知道每个数据包的实际长度了。
二、发送端将每一个数据包封装为固定长度(不够的能够经过补0填充),这样接收端每次从接收缓冲区中读取固定长度的数据就天然而然的把每一个数据包拆分开来。
三、能够在数据包之间设置边界,如添加特殊符号,这样,接收端经过这个边界就能够将不一样的数据包拆分开。

TCP报文头部多长?整个报文最长多长?

TCP数据包大小 1500 - IP头(20B)- TCP头(20B) = 1460B 

Cache和Buffer的区别

1. Cache:缓存区,是高速缓存,是位于CPU和主内存之间的容量较小但速度很快的存储器,由于CPU的速度远远高于主内存的速度,CPU从内存中读取数据需等待很长的时间,而  Cache保存着CPU刚用过的数据或循环使用的部分数据,这时从Cache中读取数据会更快,减小了CPU等待的时间,提升了系统的性能。

    Cache并非缓存文件的,而是缓存块的(块是I/O读写最小的单元);Cache通常会用在I/O请求上,若是多个进程要访问某个文件,能够把此文件读入Cache中,这样下一个进程获取CPU控制权并访问此文件直接从Cache读取,提升系统性能。

2. Buffer:缓冲区,用于存储速度不一样步的设备或优先级不一样的设备之间传输数据;经过buffer能够减小进程间通讯须要等待的时间,当存储速度快的设备与存储速度慢的设备进行通讯时,存储慢的数据先把数据存放到buffer,达到必定程度存储快的设备再读取buffer的数据,在此期间存储快的设备CPU能够干其余的事情。

Buffer:通常是用在写入磁盘的,例如:某个进程要求多个字段被读入,当全部要求的字段被读入以前已经读入的字段会先放到buffer中。

构造顺序

基类构造函数,派生类对象成员构造函数,派生类自己的构造函数

析构顺序

派生类自己的析构函数、对象成员析构函数、基类析构函数

reserve,resize,size,capacity:

resize()

既分配了空间,也建立了对象。这里空间就是capacity,对象就是容器中的元素

reserve()

reserve()表示容器预留空间,但不是真正的建立对象,须要经过insert()或push_back()等操做建立对象。push_back是在finshish处construct

相关文章
相关标签/搜索