Bootstrap

游戏客户端面经整理

1、const是什么?类中const函数如何改变变量?

答:const修饰一个变量的时候,该变量不能被修改。类中的const函数可以通过move的方式来改变

思路:从修饰什么变量开始说:①基本数据类型②引用和指针☂函数④类(成员变量、函数参数、成员函数)
①基本数据类型:放在前后都可以,被修饰的量不能改;
②指针变量和引用变量:const* 常量指针(是一个指针,它指向一个常量)、*const(是一个常量的指针)
☂函数:通常用在const函数参数,在调用函数的时候用相应变量初始化const常量,在函数中保护了传入的属性
④类:
修饰成员变量,只在对象的生命周期内是常量,同一个类的不同对象可以是不一样的,所以不能在类的声明中初始化const成员变量,只能在初始化列表中进行,因为每个对象的const常量在未创建的时候是未知的;修饰函数参数同上;修饰成员函数,防止成员函数修改任何类型的成员变量,因此不能调用非const成员函数。

const函数中可以修改mutable修饰的变量,mutable意为“可变的”,是突破const的限制而设置的

2、new、malloc

答:new是基于malloc的方法,他们都用于创建一个新指针,new相对于malloc而言在创建对象的时候先用malloc取内存空间 然后调用对象的构造函数。new和delete搭配使用,malloc和free搭配使用。

思路:从搭配+是什么+执行过程+返回类型+为什么要new说
搭配:new/delete malloc/free
是什么:new是关键字 malloc是库函数
执行过程:new先调用operator new()函数申请空间,这个函数底层是malloc实现的,然后调用构造函数初始化。new需要无需指定分配空间大小;malloc需要指定
返回类型:new返回对象的指针类型,malloc返回内存的指针,需要强制类型转换为对象类型
为什么需要new:为了满足C++对动态对象的需求

3、extern

答:在需要用到别的语言写代码时,比如C语言,加上ertern C,后续的代码会自动用C的方式编译
思路:从意义+具体说
意义:用来说明“此变量/函数在别处定义,在此处引用”,可以加速程序的编译过程节省时间
具体:另一种作用是extern "C"的用法,在C++中调用C语言库函数时告诉链接器用C函数规范链接

4、vector,push_back底层、和emplace_back区别

答:STL容器…无言以对,push_back和emplace_back应该是在扩充时空间不够的情况下有区别,emplace_back会报错?
思路:从容器形式+迭代器情况+如何扩充说
容器形式:是一个连续的线性动态空间,内部机制会自从扩充空间来容纳新元素
迭代器情况:三个迭代器,一个指向目前空间头、一个指向目前使用的空间尾、一个指向目前可用的空间尾
如何扩充:新元素插入时如果容量够就直接插入,如果不够扩充到原来的两倍,还不够就再扩充,直到足够。扩充不是在原空间扩充,是重新申请那么大的连续空间,扩充后迭代器失效

push_back在空间不够时:重新分配更大内存+拷贝原数据到新空间+释放原内存
emplace_back是就地构造,更高效

4、map unordered_map

答:都是存储一对对key+value,不同的是map是有顺序地存储而unordered_map是无顺序的,map的底层实现是红黑树,unordered_map是哈希表,unordered_map的查找速度是O(1),map是O(logN)
思路:从底层实现+元素有序+查找速度说

5、哈希表

答:通过一个哈希函数计算存储值的哈希值,哈希值一般是不相同的,存储值被放在哈希值对应的位置上,可以快速取出
思路:是什么+特点+实现+冲突
是什么+特点:哈希表也叫散列表,特点为在插入、删除、查找等操作上都是常数平均时间,也可视为一种字典结构
实现:通过哈希函数将输入值映射到表中的一个位置来访问记录,以加快查找速度,理想的哈希函数对不同的输入应该产生不同的结果
冲突:当哈希函数对不同的输入产生相同的结果,就会发生碰撞,最多的冲突解决方法是链地址法

链地址法:对每个hash值建立一个单链表,发生冲突时记录

6、多态、虚函数底层

答:多态主要是基于虚函数实现的,类中被定义为虚的函数可以在子类中进行重写,是的同一个类可以对相同的输入产生不一样的结果,表现出多态性。虚函数通过维护一个虚函数表来实现,每个对象会存储一个虚表指针,通过虚表指针访问虚函数表来取相应的虚函数
思路:什么是多态+实现方法
什么是多态:多态就是不同继承类的对象对同一消息做出不同的响应,基类指针指向或绑定到派生类的对象,是的基类指针呈现不同的表现方式。
实现方法:通过虚函数实现,基类的函数前+virtual关键字,在派生类中重写,运行时根据对象的实际类型调用相应函数。虚函数的地址保存在虚函数表中,虚函数表的地址保存在含有虚函数类的对象所指向的内存空间当中。(每个类使用一个虚函数表,每个对象用一个虚表指针)

内存布局:可以看这个回答,讲得太棒了!

7、智能指针

答:主要用于解决内存泄露的问题,四类:shared_ptr、unique_ptr、auto_ptr、weak_ptr。shared_ptr会记录指向同一个对象的指针的个数,当指针个数为0是销毁对象;unique_ptr使对象和指针一一对应;auto_ptr;weak_ptr是shared_ptr的弱化版本?需要转化成shared_ptr才能使用,打破指针的循环引用导致的无法销毁问题
思路:干什么+有哪些+各自的作用
干什么:智能指针的作用是管理一个指针,防止我们申请空间后在函数结束时忘记释放,造成内存泄露问题
有哪些:auto_ptr、unique_ptr、shared_ptr、weak_ptr
各自的作用:
auto_ptr:
unique_ptr:独占式拥有,保证同一时间内只有一个智能指针指向对象
shared_ptr:共享式拥有,多个智能指针可以指向相同对象,该对象和其相关资源会在“最后⼀个引⽤被销毁”时候释放
weak_ptr:不控制对象⽣命周期的智能指针,它指向⼀个 shared_ptr 管理的对象,只提供了对管理对象的访问手段,其构造和析构不会造成引用记数的增加或减少,用来解决shared_ptr相互引用的死锁问题

手撕shared_ptr

8、堆、栈的区别

答:栈存放全局变量、函数参数、返回地址,由系统编译器自动分配和释放;堆是用户申请的内存空间,由程序员释放,在程序运行结束后有系统自动释放 系统会自动回收
思路:从申请方式+申请后系统响应+空间状态+申请效率+存放内容说
申请方式:

申请方式系统自动分配程序员主动申请
申请后系统响应如果剩余空间大于申请空间则分配成功,否则分配失败栈溢出堆在内存空间中呈现的方式类似链表,在链表中寻找第一个大于申请空间的节点分配给程序,将该节点从链表中删除,大多数系统中该块空间的首地址是本次分配空间的大小,便于释放,将该块空间的剩余空间再次连接在空闲链表上
空间状态栈在内存中是连续的一块空间(向低地址扩展)堆在空间中是不连续的
申请效率系统自动分配,效率高,程序员无法控制程序员主动申请,效率低,用起来方便但容易产生碎片
存放的内容局部变量、函数参数、返回地址程序员控制

9、线程和进程,一个进程的内存结构

思路:是什么+干什么的+区别是什么
进程:可认为是程序执行的一个实例,是系统资源分配的最小单位,每个进程拥有独立的地址空间;一个进程无法访问另一个进程的变量和数据结构,需要进行进程间通信
线程:是操作系统能够进行运算调度的最小单位,被包含在进程中,是进程的实际运作单位。程序员可以通过它进行多处理器变成,可以使用多线程对运算密集型任务体素。(一个线程100ms,十个线程10ms)。一个程序至少有一个进程,一个进程至少有一个线程

进程线程
地址空间不同进程之间地址空间独立同一进程的线程共享该进程的地址空间
资源不同进程之间资源独立同一进程的线程共享该进程的资源
崩溃一个进程奔溃后在保护模式下不影响其他进程一个线程奔溃后整个进程奔溃->(多进程>多线程robust)
切换消耗资源大消耗资源相对小
并发执行可以可以
执行每个独立进程有一个程序入口和出口不能独立执行,必须依存于应用程序提供的多线程控制

请添加图片描述
游戏服务器应该为每个用户开辟一个线程还是一个进程?
游戏服务器应该为每个用户开辟一个进程,因为同一进程的不同线程之间会相互影响,一个线程出现异常(比如用户掉线等)会影响其他的线程,可能会导致进程的崩溃。因此,为保证不同用户之间不会相互影响,应该为每个用户开辟一个进程
线程共享哪些资源?

线程共享线程独享
地址空间、全局变量、打开的文件、子进程、闹铃、信号及信息服务程序、记账信息程序计数器、寄存器、栈、状态字

10、锁

思路:有哪些锁+死锁的四个必要条件
C++有哪些锁:互斥锁、条件锁、自旋锁、递归锁
互斥锁:避免多个线程在同一时刻同时操作一个共享资源
条件锁:就是条件变量,当某线程因为某个条件未满足可以使用条件变量使该程序处于阻塞状态。
自旋锁:
递归锁:用于保护共享数据受到多个线程的访问

死锁:指多个进程循环等待它方占有的资源而无线僵持的局面。①互斥(一个资源只能被一个线程使用,打印机等);②占有且等待(A进程占有一部分资源,且A进程在等待B进程的资源);☂不可强行占有(资源不能被强行占有,如只能一个人过独木桥,不能把人推下去自己过);④循环等待(A等待B资源, B等待A资源)

如何避免?
银行家算法:在分配资源之前,判断系统是否安全,安全才分配。
①进程的请求是否比进程剩余可用少;②进程请求是否比系统当前可用少;☂试探分配资源,修改相关数据;④执行安全性检查,安全则分配

什么是递归锁?
递归锁(recursive mutex)和⾮递归锁(non-recursive mutex)。同一个线程多次获取同一个递归锁不会产生死锁,同一个线程多次获取同一个非递归不会产生死锁

11、虚拟内存

思路:是什么+优缺点+与进程关系
是什么:是一种内存管理技术,会使程序认为其拥有一块很大且连续的内存,而操作系统完成从虚拟内存的虚拟地址到真实内存或者硬盘地址之间的映射工作,缓解内存大量使用时的内存紧张
优缺点:可以弥补物理内存空间大小的不足,提高程序的反应速度,减小对物理内存的读取从而保护内存延长内存使用寿命;占用一定物理硬盘空间,加大对硬盘的读写,设置不当会影响整机稳定性和速度
与进程关系:又称虚拟存储器,是一个抽象概念,他为每一个进程提供一个假象,好像每个进程都在独占地使用主存。每个进程看到的存储器是一致的,称为虚拟地址空间。虚拟地址空间是对于单一进程的概念,该进程看到的将是地址从0000开始的整个内存空间。从最低的地址看:程序代码和数据、堆、共享库、栈、内核虚拟存储器。大多数计算机字长为32限制了虚拟地址空间为4G

12、TCP和UDP

1、TCO是面向链接的;UDP是无链接的,即发送数据之前不需要建立链接
2、TCP提供可靠的服务;UDP不保证可靠交付
3、TCP面向字节流,把数据看成一连串无结构的字节流;UDP是面向报文的
4、TCP有拥塞控制;UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,像视频会议)
5、每一条TCP连接只能是点到点的;UDP支持一对一、一对多、多对一、多对多的通信方式

TCPUDP
特点传递任意长度的消息、可靠、流量控制、拥塞控制一对多通讯、效率高、简单、实时性好、无队头阻塞问题
应用场景发送消息、浏览网页之类的场景,需要确保用户消息不丢失视频聊天或看直播

13、多线程一致性

思路:是什么+方式
是什么:线程同步是指多线程通过特定的设置来控制线程之间的执行顺序,换言之线程之间通过同步建立了执行顺序的关系
方式:临界区、互斥对象、信息量、事件对象

方式原理特点
临界区通过对多线程的串行化来访问公共资源或一段代码;即只有一个线程能够访问公共资源,公共资源有人访问的时候,后来的线程需要排队速度快、适合控制数据访问
互斥对象拥有互斥对象的线程才能访问公共资源,拥有互斥对象的线程处理完任务后需要交出互斥对象,互斥对象只有一个表现上和临界区很像
信息量允许多个线程在同一时刻访问同一资源,但限制在同一时刻访问次资源的最大线程数目创建是设置最大资源数和当前可用资源数,一般相等,每增加一个线程当前可用资源-1,线程处理完离开后当前可用资源+1,当前可用资源为0时不允许线程进入
事件对象通过通知操作保持线程同步方便对多个线程的优先级比较

对共享资源上锁,拿到锁的线程才可以访问共享资源,可以强制是的对共享资源的访问是顺序的

14、判断点在三角形内的方法

将点与三角形的三个顶点相连,构成三个小三角形,如果三个小三角形的面积大于初始三角形则在三角形外,如果等于则在三角形内

15、进程通信方式

思路:有哪些+特点
有哪些:管道+FiFO+消息队列+信号量+共享内存

特点
管道①半双工的(数据只能在一个方向流动),具有固定的读写端口;②只在有亲缘关系的进程间通信;☂可看做特殊的文件,具有读写函数,但不是普通文件,不属于任何文件系统,只存在于内存中
FIFO①可在无关的进程之间交换数据;②有路径名与之关联,以一种特殊设备文件形式存在与文件系统中
消息队列①是消息的连接表,存放在内核中,一个消息队列由一个标识符来标识;②消息队列面向记录,其中的消息具有特定的格式以及特定优先级;☂消息队列独立于发送接受进程,进程终止时消息队列的内容不会删除;④可实现消息的随机查询
信息量①是一个计数器,用于实现进程间的互斥与同步,不是用于存储进程间通信数据;②信息量用于进程间同步,传递数据需要结合共享内存;④信息量量基于操作系统的PV操作,程序对信息量的操作都是原子操作
共享内存①指两个或多个进程共享一个给定的存储区;②是最快的进程通信方式,因为对内存直接存取;☂因为多个进程同时操作需要进行同步,通常和信息量结合

16、三次握手和四次挥手

TCP协议在是7层网络协议中的传输层协议,负责数据的可靠传输
TCP三次握手:用于建立TCP链接
1、客户端发送一个SYN=1,seq=x,表示
2、服务端接受SYN后,给客户端发送一个SYN_ACK
3、客户端收到SYN_ACK后再给服务端发送一个ACK
为什么要三次握手?
思路:为什么握手?为什么三次?
为什么握手:因为要保证数据传输的可靠性
为什么三次:一次握手无法判定发送成功与否;若只有二次握手的话,假设客户端发送一个请求后由于网络延时服务端没有收到,在等待一段时间后客户端重新发送一个请求,这次服务端成功收到请求,并且也收到了由于网络延时的第一次请求,服务端会为两个连接都申请资源(因为对于服务端来说两个连接请求都是新的)并返回ACK,但是第一次请求的SYN是个无效请求,A收到后不会理会,B在不知道的情况下会一直维持资源造成浪费;三次请求就没有这个问题;四次请求就多余了。

TCP四次挥手:用于断开TCP连接
1、客户端想服务端发送FIN
2、服务端接受FIN后想客户端发送ACK,表示接受到了断开链接请求。此时客户端不再发送数据,但是服务端需要处理剩余数据。
3、服务端处理完数据后,想客户端发送FIN,表示客户端可以断开连接
4、客户端收到服务端的FIN,想服务端发送ACK,表示服务端也可断开链接。服务端收到后断开,客户端等到两倍MSL时间后断开
为什么要四次挥手?
应为相对于三次握手,服务端在收到第一次请求时可能还有数据要处理,不能立即关闭,需要在服务端准备好后再给客户端发FIN,所以相比三次握手多了一次
为什么要等待两倍MSL时间
MSL时间:报文最大生存时间,超过这个时间的报文将会被丢弃
最坏的情况为客户端发送第三次的ACK,而ACK刚好经过MSL的时间到达服务端,此时服务端又刚好重新发送了一个FIN请求(这是最晚的情况,因为再晚服务器收到ACK直接关闭了),FIN又刚好经过MSL时间到达客户端。也就是说,客户端在发送第三次的ACK后最坏在2MSL后收到服务端的新FIN,超过这个时间节点即表示服务端已经关闭或者服务端的重传的FIN由于网络太差无法到达了,此时直接关闭(等待已经没有意义)

17、守护进程、孤儿进程、僵尸进程

守护进程:是在后台运行的特殊进程,它独立于控制终端并周期性地执行某种任务或等待处理某些发生的事件
一般情况下,子进程由父进程创建,两者退出无顺序,但正常情况父进程会先与子进程结束并调用等待函数等待子进程完成再退出
孤儿进程:当父进程不等待直接退出,剩下的子进程会被init(pid=1)接收,成为孤儿进程
僵尸进程:如果子进程先退出,且父进程没有结束或调用等待函数获取子进程状态,子进程残留下来的状态信息就变成了僵尸进程

18、中断的实现与作用?

①关中断,进入不可再次响应中断的状态(硬件)
②保存断点,为了在中断结束后能正确返回中断点(硬件)
☂将中断服务程序的入口地址送PC,转向中断服务程序(软硬兼可)
④保护现场、置屏蔽字、开中断,这保护了CPU某些寄存器的内容、设置中断处理的顺序、允许更高级别的中断得到响应(软件)
⑤设备服务,实际上有效的中断处理工作(软件)
⑥退出中断。进入不可中断状态->关中断->恢复屏蔽字->恢复现场->开中断->中断返回

CPU中断?
① 计算机处于执⾏期间;
② 系统内发⽣了⾮寻常或⾮预期的急需处理事件;
☂ CPU暂时中断当前正在执⾏的程序⽽转去执⾏相应的事件处理程序;
④ 处理完毕后返回原来被中断处继续执⾏
作用:
① 可以使CPU和外设同时⼯作,使系统可以及时地响应外部事件;
② 可以允许多个外设同时⼯作,⼤⼤提⾼了CPU的利⽤率;
☂ 可以使CPU及时处理各种软硬件故障。

19、系统调用和函数调用

系统调用:①操作系统提供给用户程序调用的一组特殊接口。用户程序可以通过这组特殊接口获得操作系统内核提供的服务;②可以用来控制硬件、设置系统状态、读取内核数据、进程管理;
函数调用:①运行在用户空间;②通过压栈操作进行函数调用

函数库调用系统调用
在用户地址空间执行在内核地址空间执行
运行时间属于"用户时间"运行时间属于"系统时间"
属于过程调用,开销小需要在用户空间和内核上下文环境切换,开销大
c函数库libc大约300个函数UNIX大约90个系统调用
system、fprintf\mallocchdir、fork、write
;