本系列给出了在复习过程中一些C++后台相关面试题,回答内容按照笔者的知识点掌握,故有些问题回答较为简略
1、信号的生命周期
一个完整的信号生命周期可以用四个事件刻画:1)信号诞生;2)信号在进程中注册完毕;3)信号在进程中注销完毕;4)信号处理函数执行完毕。
信号诞生:某个事件发生,触发相应信号;
信号注册:Linux中分为非实时信号(signal函数注册)和实时信号(sigaction函数注册,可以支持信号带有参数,signal不支持),从kernel对非实时信号和实时信号的处理中进行描述。
信号注销:进程在执行信号处理函数之前,首先要把信号在进程中注销,对于实时信号如果还存在多个排队的信号,其在信号注销之后不会在信号集中删除该信号;
执行信号处理函数:执行函数。这里涉及到内核从kernel返回user处理信号的过程:信号在程序上下文在kernel中的时候到来,在返回user时信号被响应,此时运行环境依然为内核态,将信号处理
函数栈帧压入到内核态栈帧,此时弹栈后返回到用户层执行信号函数,执行完毕后弹栈又重新回到内核态,继续处理系统调用返回,重新返回到用户态运行(即信号处理的两次陷入内核)
2、信号的产生方式
(1)终端按键产生信号,比如ctrl c
(2)硬件异常产生信号,比如执行除以0的指令,进程访问非法内存地址
(3)软件产生信号,比如kill函数
3、信号的处理方式
(1)忽略信号;
(2)执行信号的默认处理动作;
(3)执行自定义信号处理函数
4、如何消除隐式转换
如果c++类的构造函数有一个参数,那么在编译的时候有一个缺省的转换操作:将该构造函数对应数据类型的数据转换为该类型对象。
比如:
class Test
{
public:
Test(int num){} //定义为explict Test(int num){},消除隐式转换,下述编译不能通过
} Test obj =
上述代码中编译器将自动将整形转换为Test类对象,实际上等同于Test obj(10),这种是显示转换。如果有一个转换Test obj='a',实际上num将等于97,这种不符合本类的设计初衷。为了防止这种隐式转换,C++定义explicit关键字,被修饰的构造函数不能发生相应的隐式类型转换,只能以显示方式进行类型转换。
5、重载,重写和隐藏的区别
这个回答后续补充
6、volatile表示什么?有什么作用?
该部分内容解释,详见这里
7、malloc和new的区别,free和delete的区别
malloc和free是c语言的标准库函数,new和delete是c++的运算符,都可以用于动态申请或释放内存。对于内部数据类型的对象(int, char等),采用malloc和new都可以完成对对象内存的分配,同时函数返回所分配内存的首地址。但是对于非内部数据类型对象,采用malloc和free不能完成内存分配,对象在创建时需要执行构造函数,在释放时需要执行析构函数,需要new与delete运算符完成上述内存的分配。malloc与free,new和delete必须配对使用,避免内存泄露。
8、free一个数组时如何知道要释放多大的内存
在malloc一块内存地址时,现代编译器会把内存大小数值放在分配地址开始的位置,从而使得free该块内存时,可以知道需要释放多大的内存。
9、Linux内部提供了哪些调试宏
__FILE__:文件名;__DATE__:日期 __TIME__:时间 __LINE__:当前代码行 __FUNCTION__:所在函数名称
测试代码:
#include <stdio.h> int main()
{
printf("The file is %s.\n",__FILE__);
printf( "The date is %s.\n", __DATE__ );
printf( "The time is %s.\n", __TIME__ );
printf( "This is line %d.\n", __LINE__ );
printf( "This function is %s.\n", __FUNCTION__ ); return ;
}
结果:
The file is macro.c.
The date is Aug .
The time is ::.
This is line .
This function is main.
10、手写线程安全的单例模式
分为懒汉模式和饿汉模式,详见这里。
11、引用和指针的区别
(1)引用是给另一个变量起的别名,所以引用不会额外分配内存空间。指针是一个实体,需要额外分配内存空间;
(2)引用在定义时必须进行初始化,并且之后不能够改变,指针在定义时不一定需要进行初始化,且指向的内容可以变化;
(3)有多级指针,但没有多级引用;
(4)指针和引用的自增运算结果不一样,指针自增指向下一个内存单元,引用自增是将遍历加1;
(5)sizeof引用得到的是引用变量的大小,sizeof指针得到的是指针变量的大小(32位为4字节,64位为8字节);
(6)引用访问一个变量是直接访问,指针访问变量是间接访问。
详见这里。
12、pthread_cond_signal和pthread_cond_broadcast的区别
条件变量。前者用于唤醒一个等待条件的进程,后者用于唤醒所有等待条件的进程(惊群问题)。
13、如果用map删除了一个元素,迭代器还能用吗?为什么?怎么样做可以再接着用。
详见这里。
14、TCP三次握手和四次挥手及各自的状态。
结合《Unix网络编程卷1》。
(一)三次握手:
客户端 服务端
SYN_SEND
SYN_RECV
ESTABLISH ESTABLISH
(二)四次挥手:
客户端 服务端
FIN_WAIT1
CLOSE_WAIT
FIN_WAIT2
LAST_ACK
TIME_WAIT
CLOSED CLOSED
15、TCP如果两次握手会出现什么问题
如果二次握手的ACK丢失,客户端不能判断当前是否已经建立连接。
16、TCP四次挥手为什么要有TIME_WAIT状态?
客户端在接收服务端最后一次消息发送后,回复消息,状态会变为TIME_WAIT,并等待2MSL(MSL为报文的最大存活时间),如果服务端没有接收到客户端最后的恢复消息,会重新发发送最后一次关闭数据,数据包来回时间为2MSL。
17、死锁的原因
(1)死锁的四个条件:
互斥条件:描述一个资源只能被一个进程所占用
占用和等待条件:已经占有资源的进程会由于请求其他进程占用的资源而导致等待
不可剥夺条件:一个进程需要请求被其他进程占用的资源时,不能抢占该资源,只能通过其他进程释放
循环等待条件:死锁发生时,系统中存在着进程与资源的环路。
(2)避免死锁的方法:银行家算法
(3)预防死锁的方法:破坏四个必要条件
破坏互斥条件:一切都使用假脱机技术
破坏占用和等待条件:在开始时请求所有资源
破坏不可剥夺条件:使资源可剥夺
破坏环路等待条件:给所有资源按升序编号,一个进程每次只能请求比当前资源序号高的资源。
18、为什么要字节对齐
详见这里。
19、进程间通信的方式有哪些?线程间通信的方式有哪些?
进程间通信:信号,信号量,消息队列,管道(有名管道和无名管道),共享内存,socket
线程同步方式:互斥锁和读写锁,条件变量;信号量,临界区。
20、解决hash冲突的方法
详见这里。
21、C++的内存分为几个部分
Linux进程虚拟地址空间。详见这里。
22、如何得到一个结构体成员的偏移量?
类比于Linux内核进程current宏实现。详见这里。
23、进程与线程的区别
(1)进程是资源分配和调度的独立单元,线程是CPU调度的基本单元;
(2)同一个进程中可以包括多个线程,并且线程是共享进程资源的(这里共享只包括,写时拷贝的内存页,文件描述符,信号),但是线程独有的(task_struct进程结构体,内核栈,寄存器)
(3)线程结束不会影响到同一进程中的其他线程,但进程结束会影响到所有线程;
(4)由于线程共享同一进程的数据资源,所以对于数据的访问需要保证同步与互斥问题。
24、对一个数组而言,delete a和delete []a有什么区别?
delete a只会删除这个数组的第一个元素,其后的元素内存不会被释放掉,从而导致内存泄露。delete []a将会释放所有元素内存
25、IO模型主要有哪些
(1)同步阻塞IO:即传统的IO模型;
(2)同步非阻塞IO:默认创建的socket都是阻塞的,非阻塞IO要求socket被设置为NONBLOCK
(3)IO多路复用:Reactor设计模式,常用的select和epoll
(4)异步IO:Proactor模式,也称为异步非阻塞IO。
同步和异步的概念描述的是用户线程与内核的交互方式:同步是指用户线程发起IO请求后需要等待或者轮询内核IO操作完成后才能继续执行;而异步是指用户线程发起IO请求后仍继续执行,当内核IO操作完成后会通知用户线程,或者调用用户线程注册的回调函数。
阻塞和非阻塞的概念描述的是用户线程调用内核IO操作的方式:阻塞是指IO操作需要彻底完成后才返回到用户空间;而非阻塞是指IO操作被调用后立即返回给用户一个状态值,无需等到IO操作彻底完成。
详见http://www.cnblogs.com/fanzhidongyzby/p/4098546.html
26、select,poll,epoll的区别
略。
27、TCP的nagle算法和延迟ACK,具有什么好处,但如果在一起使用会出现什么问题。
详见这里。
28、epoll的LT模式和ET模式
详见这里。
LT模式,如果有数据未处理完,会一直触发处理。ET模式,数据必须要一次性完成处理(采用while循环读入数据),否则下次不会被触发处理流程
一个面试问题:LT模式下,如果socket可写,将会一直触发可写,如何解决?
答:在需要写socket时,将其加入epoll的event,触发可写时进行写操作,写完后通过epoll_ctl将其移出event,从而避免重复触发可写。缺点在于:如果在发送数据量较少时,仍然需要两次epoll_ctl操作,会造成性能损耗。
优化方法:在需要写socket时,直接写socket,如果发生eagain或ewouldblock,再将其加入到epoll的event,判断其可写时再写。