oppo面经

用户态与内核态的区别以及区分的原因

https://www.cnblogs.com/gizing/p/10925286.html

https://blog.csdn.net/qq_39823627/article/details/78736650

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

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

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

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

定义和声明的区别

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

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粘包或拆包有很多原因,现列出常见的几点,可能不全面,欢迎补充,
1、要发送的数据大于TCP发送缓冲区剩余空间大小,将会发生拆包
2、待发送数据大于MSS(最大报文长度),TCP在传输前将进行拆包
3、要发送的数据小于TCP发送缓冲区的大小,TCP将多次写入缓冲区的数据一次发送出去,将会发生粘包
4、接收数据端的应用层没有及时读取接收缓冲区中的数据,将发生粘包

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

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

 



 

posted @ 2019-10-19 20:16  李湘沅  阅读(379)  评论(0编辑  收藏  举报