转载请注明t1234xy4原创:http://blog.csdn.net/t1234xy4/article/details/52105429
1、heap 与stack 的差别
(1)申请方式不同 stack有系统分配、heap由程序员申请(malloc/ new)
(2)申请后系统响应
stack:只要栈中剩余空间大于申请空间,系统就提供内存,否则报告异常。
heap:操作系统有一个空闲的地址记录链表,系统收到heap申请时,会遍历链表,找到最近的满足申请的空间并分配给程序,然后操作系统会将该节点从链表中删除,并将多余的空间重新加入到空闲链表中。
(3)申请大小限制
stack:栈向低地址扩展,是连续的内存区域,能够分配的空间较小。
heap:堆向高地址扩展,是不连续的内存区域,获得的空间比较灵活,而且比较大。
(4)效率比较
stack:由系统自动分配,效率高,速度快。
heap:由程序员调用malloc/new 分配,速度较慢,容易产生内存碎片。
(5)堆和栈的存储内容
stack:在函数调用时,入栈:第一个进栈的主函数的下一条指令的地址,然后是函数的参数,接着是函数的局部变量。退栈:局部变量退栈、函数的参数、最后是主函数的下一条指令的地址,由改点继续执行。
heap:在堆的头部用一个字节存放堆的大小,存储内容由程序员安排。
(6)栈的存储效率比堆快。
xy
2、程序运行空间结构
C/C++编译时,内存分为:堆、栈、程序代码区、
静态区(初始化全局变量与静态变量、未初始化的
静态变量与全局变量)、文字常量区等。
栈区:存放函数的参数、局部变量。
堆区:new/malloc申请,delete/free释放。
程序代码区:存放带执行的代码段。
静态数据区:全局初始化数据及静态初始化数据。
全局未初始化及静态未初始化数据。
文字常量区:常量字符串。
上下文等。
3、select 、 poll 、epoll区别
相同点:
(1)select、poll 、epoll都是I/O多路复用的机制。I/O多路复用就是一种机制可以监听多个描述符,一旦某个描述符就绪,就通知相应的程序进行相应的操作。
不同点:
函数原型:
select:int select(maxfd,set_readfd,set_writefd,set_expfd,timeout);
poll:int poll(maxfd,pollfdArray,maxfd, options)
structpoll_fd{
int fd;
short event;
short reevent;
}
epoll: 分为epoll_create(size)创建epoll句柄,同时创建一个eventpoll结构体
epoll_ctl(epollfd,option,fd,structepoll_event)
option:告诉添加、删除、修改fd
struct epoll_event 告诉监听的类型:EPOLLIN,EPOLLOUT,EPOLLERR,EPOLLHUP,EPOLLET等
主要添加描述符,设置监听事件类型
epoll_wait(epollfd,structevents,maxfd,timeout)
从就绪链表中拷贝已就绪的描述符到events中
select实现原理:
poll与select内部原理相似,只是借口不同,他们的大致原理如下:
1)调用copy_from_user函数将外部描述符拷贝到内核;
2)将当前进程加入就绪队列;
3)轮到当前进程执行时,遍历所有的描述符,检测是否有描述符就绪,没有就进入睡眠,过一段时候唤醒,查找所有的描述符,如果有描述符就绪;
4)就将就绪的描述符拷贝到用户态。
epoll实现原理:
1)使用epoll_create创建句柄,同时创建一个eventpoll结构体,eventpoll结构体中维护着一个红黑树,用于保存描述符句柄,同时还维护一个双向链表,用于保存已经就绪的描述符句柄。
2)调用epoll_ctl函数,添加(修改、删除)需要监听的描述符,同时设置监听类型,内核将会把描述符添加到红黑树中,并对每一个描述符设置回调函数。
3)如果有事件响应,回调函数会把响应的描述符句柄添加到双向链表中,每次只需要查询链表是否为空即可判断有无事件响应。
epoll相比select优点:
1)select每次监听都需要将描述符集从用户态拷贝到内核态,如果描述符集过大,消耗较大拷贝时间;
2)select默认支持最大监听数为2048,默认值为1024,因不同的内核值可能不一样,可以通过修改FD_SETSIZE的值,然后重编译内核修改;而epoll的最大支持数只受最大打开文件描述符数限制。
3)epoll内部维护着一个双向链表,就绪的描述符就保存在链表中,每次调用wait后直接查询链表是否为空就可知有没有描述符准备好,而不用轮询查询;当描述符数据急剧增加时,select的性能直线下降,而epoll并不受影响。
epoll的ET与LT(边沿触发与水平触发)模式:
1)epoll默认设置为水平触发,可以通过设置epoll_create中的epollevent 中的POLLET来设置为边沿触发。边沿触发与水平触发最直接的差别是边沿触发只返回一次,如果描述符中buffer没有读完,内核不再通知,直到下次事件到来。 水平触发没有读完buffer中的数据,内核会再次通知用户读取数据。因此使用ET时,需要确保数据已经全部读出。
2)ET边沿触发必须使用非阻塞的套接字,(避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死),LT可以是阻塞也可以设置为非阻塞。
3)ET是高速工作方式,只支持非阻塞的socket,速度比水平触发要快。
4、智能指针有哪些,及他们的区别?
std::auto_ptr
boost::scoped_ptr 、boost::shared_ptr、boost::weak_ptr
std::tr1::shared_ptrstd::tr1::weak_ptr
std::auto_ptr :
(1)、由于auto_ptr被销毁时,会自动释放所指对象。因此,不能让多个指针指向auto_ptr所指对象。auto_ptr的复制操作ptrA=ptrB;会让ptrA获得ptrB资源的唯一使用权,ptrB将会被置为null。
(2)、std::tr1::shared_ptr 采用引用计数,内部会记录有多少个shared_ptr指向同一对象,当失去最后一个指针指向该对象时,即引用计数变为0时,该对象将被删除。(无法处理环状指针)
(3)、std::tr1::shared_ptr的构造函数可以接受两个参数,一个是被管理的指针、另一个是引用计数变为0时的删除操作,支持定制的删除器,可以防范DLL问题。
(4)、std::tr1::weak_ptr 用来解决环状指针,weak_ptr并不参与引用计数。
(5)、在对象auto_ptr与shared_ptr的析构函数中都是delete,而不是delete[],因此不能用他们指向array。
(6)、 boost::scoped_ptr 与auto_ptr类似,区别在于scoped_ptr严格限制不能拷贝,因此不能转换所有权,同样内部是delete,而不是delete[],不能用于array等管理。
(7)、boost::shared_ptrboost::weak_ptr 与std::tr1::shared_ptrstd::tr1::weak_ptr相同的用法。
总结:
(1) auto_ptr:智能指针常用于管理对象,在指针被销毁时,所指对象也会被释放。不能共享对象管理权。auto_ptr可以拷贝,但是拷贝后所有权转移,原指针变为null。
(2)scoped_ptr:与auto_ptr相似,但scoped_ptr明确限制指针不能被拷贝。其所有权不共享也不会转移。
(3)shared_ptr:采用引用计数的方法,当增加一个指针对该对象进行管理时,引用计数增加1,反之减1,当引用计数为0时,所指对象会被释放。shared_ptr用于管理对象时,可以共享管理权,但是shared_ptr不能打破循环互指的情况。
(4)weak_ptr:是一种弱的指针,weak_ptr可以指向shared_ptr和weak_ptr的对象,但shared_ptr的引用计数不会增加,引入weak_ptr主要是为了对shared_ptr对象的访问,打破循环互指的情况。
5、重写(覆盖) 多态 重载
重写:子类对父类中虚函数重写,其中函数名、返回值、参数、const等都必须一样,在子类中,子类的函数覆盖掉父类的虚函数。
重载:子类对父类成员函数的重载,其中函数名相同,返回值或参数个数或参数类型或者const类型不同。
多态:使用父类指针指向子类实例,执行过程中虚函数指针动态的绑定执行函数,这一动态联编过程叫做多态。
重载的概念不属于面向对象编程,与多态无关。当子类重定义了父类的虚函数后,父类指针根据子类指针的类别,动态的调用该子类的该函数。多态的实现是为了接口的重用。
6、面向对象的三大特性:封装、继承、多态
7、三次握手 四次挥手过程画图理解
三次握手过程:
(1)服务器端必须准备好接收外来连接,服务器处于监听状
态;socket、bind、listen,(被动打开)
(2) 客服端主动发起请求,向服务器端发送同步分节SYN J;
(connect主动打开建立连接、同步分节是将发送的数据序列号)
(3) 服务器端接到客服端的同步分节 J,然后返回对应的
ACK J+1,并发送同步分解SYN K给客服端。
(4) 客服端收到ACK与服务器端的同步分节K后,返回
确认K+1。
由于SYN占据一个字节,因此ACK需要加1
四次挥手过程:
(1) 应用程序首先调用close发起主动关闭,发送一个FIN给
对端;
(2) 对端接受到FIN,如果有数据等待传输,立即返回ACK,
并继续传输数据,等待数据传输完成;然后再发送FIN给应用
程序;(接受到FIN后关闭接受端口,发送FIN后关闭发送端口)
(3) 应用程序接受到FIN后发送的ACK,并等待TIME_WAIT
后,关闭应用程序。(接收到FIN后关闭接受端口,等待
TIME_WAIT后关闭发送端口)
8、TCP有哪些、及状态转换图
TCP状态:
CLOSED LISTEN SYN_RCVD SYN_SENT ENSTABLISHED
FIN_WAIT_1 FIN_WAIT_2 CLOSING TIME_WAIT CLOSE_WAIT LAST_ACK
建立连接:服务器CLOSED->LISTEN->SYN_RCVD->ENSTABLISHED
客服端CLOSED->SYN_SENT->ENSTABLISHED
同时打开CLOSED->SYN_SENT->SYN_RCVD->ENSTABLISDED
同时打开两端都是服务也是客户,TCP中只建立一条连接而不是两条
关闭连接:被动关闭:CLOSE_WAIT->LAST_ACK->CLOSED
主动关闭:FIN_WAIT_1->FIN_WAIT_2->TIME_WAIT->CLOSED
同时关闭: FIN_WAIT_1->CLOSING->TIME_WAIT->CLOSED
快速关闭:FIN_WAIT_1->TIME_WAIT->CLOSED
9、TCP关闭,客户端(主动关闭端)为什么需要TIME_WAIT?
TIME_WAIT发生在主动关闭端最后发送ACK后等待,TIME_WAIT持续时间是最长分节生命期(MSL)的两倍。之所以等待原因如下:
(1)可靠的实现TCP全双工连接的终止;假如最终的ACK丢失,服务器端需要重新发送最终的FIN,因此客户端必须维护状态信息,以确保留出足够的时间让服务器重传FIN。
(2)确保老的重复分节在网络中消失。假如IP1的PORT1和IP2的PORT2有一个TCP连接。这个连接关闭后,过一段时间在相同的端口中重新建立另一个连接。为了防止这种情况中来自老的连接的出现,因此TCP必须防止之前的老的连接已经终止。
10、指针与引用的区别
(1) 指针占有空间,可以有指向指针的指针;引用不占用空间,不存在引用的引用
(2) 指针存在空值,需要判空;引用不存在空引用,没有判空,效率高于指针
(3) 指针是一个实体,引用是一个别名
(4) 指针需要解引用,引用不需要解应用
(5) 引用没有const,指针有const
(6) sizeof(指针) 是指针本身的大小,sizeof(引用)是引用对象的大小
(7) 指针的++与引用的++不同
(8) 引用是类型安全的,而指针不是(引用有类型检查)
11、如何实现多态,具体原理
(1) 多态是父类指针指向子类对象,并调用子类对应虚函数时产生的动态绑定过程。
(2) 在类中,只要存在虚函数这个类就需要维护一个虚函数表, 虚函数表中保存着每个虚函数的函数地址。
(3) 类中会存在一个指向虚函数表的虚指针指向虚函数表。
(4)如果基类存在虚函数,派生类会继承基类的虚函数表,派生内中重写基类虚函数,会覆盖掉基类中对应虚函数的地址。
12、虚函数与纯虚函数的区别
(1) 虚函数在类中申明并有确切的实现;纯虚函数只有申明,没有实现,需要子类去实现。
(2) 存在纯虚函数的内是抽象类,不能被实例化,继承抽象类的子类必须重写纯虚函数;只存在纯虚函数的类可以被实例化,且子类可以重写虚函数也可以不重写虚函数。
13、C++的四种类型转换(RTTI)
const_cast reinterpret_cast static_cast dynamic_cast
const_cast 将const类型转换为非const类型
static_cast
static_cast:与C中的数据类型转换有相同的作用。但不能移除数据类型的常量性如:const volatile,也不能将int 等内置类型转为struct类型。还可以将子类的指针转为父类指针(上行转换,下行转换也可以,但是缺少安全检测,导致错误)
const_cast:仅用于移除变量常量性限制如:const volatile
dynamic_cast:(下行转换)将父类指针或引用转换为子类指针或引用。由于存在类型安全检测,因此不成功指针返回null,引用抛出异常。dynamic_cast必须在有虚函数的类中使用。因为只有存在虚函数的类才维护虚表,而type_info的实力存放在虚表中,一般是首个表目。
reinterpret_cast:主要用于函数指针的转换,不具备移植性,通常应该尽量避免函数指针转型。
14、unix中close 与shutdown的区别
函数原型:
int close(int sockfd);
int shutdown (int sockfd,int howto);
在unix中,close与shutdown都是用来关闭套接字。
close:
(1) 由于描述符的引用计数,close一次描述符的引用计数减1,如果引用计数仍然大于0,那么套接字将不会被关闭。只有等待描述符的引用计数变为0之后才会关闭(4次挥手)。
(2) close 终止读写两个方向的数据传送。TCP是全双工的,close会关闭接收与发送两个端口。
shutdown与close不同,shutdown是直接关闭套接字。但提供了参数howto,可以单独关闭读或写端口,亦可以同时关闭读写端口。
(1) 关闭读端口,套接字不在有接收数据,缓冲区中的数据也会被丢弃。
(2) 关闭写端口,对TCP而言为半关闭,缓冲区中待发送的数据将会被发送,然后紧跟着终止序列。
(3) 关闭读写,等效于shutdown两次,shutdown读与shutdown写。
15、wait与waitpid区别
wait与waitpid都是父进程用来处理已终止的子进程。它们的函数原型:
pid_t wait(int *statloc);
pid_t waitpid(pid_t pid,int *statloc,int options);
(1) 都有两个返回参数:已终止的进程ID以及返回子进程的终止状态。通过查看statloc可以知道子进程是如何被终止的。
(2) wait是阻塞的,如果没有终止的子进程,不过存在子进程在运行,那么wait会一直在阻塞下去,知道有子进程终止为止。
(3) waitpid可以是父进程等待特定的子进程,也可以将pid设置为-1,表示等待第一个子进程。同时waitpid可以设置是否阻塞,不阻塞可以设置为WNOHANG。
(SIGCHLD,子进程终止,内核发送给其父进程的信号)
16、fork的理解
函数原型:pid_t fork();
(1) fork() 调用一次返回两次,返回 0 为子进程,返回大于0为父进程,小于0执行失败。(子进程可以有多个,父进程只能有一个,可以通过getppid得到父进程ID)
(2) fork()创建的子进程是父进程的一个副本,子进程获得父进程的数据空间、堆、栈。
(3) 子进程与父进程共享代码段。
(4) 通常fork的实现过程中,并不是直接生成一个子进程副本,而是使用写时复制的技术,将堆、栈、或者数据空间的权限设置为只读,当父进程或者子进程修改其中的值时再对这一块区域创建一个副本。(副本:堆、栈、数据段、BSS段)
17、public protectedprivate的访问权限
(1) public 修饰成员与函数对外都是可见的,既能被子类继承也能被类对象直接访问。
(2) protected 修饰的成员与函数对类的实例对象来说与private相同,在其子类中与public相同
(3) private 修饰的成员及函数对类对象与子类都是不可见的。
继承public、protected、private:
(1) public 继承保持父类中的访问权限
(2) protect继承父类中的public与protected都变为protected权限
(3) private继承父类中的所有成员与函数都变为private权限
18、static与const区别
访问权限不同:
(1)static 成员:可以被static函数、const函数、普通成员函数等读写
(2) const成员:不能被static函数读写、可以被const函数、成员函数读,但不能修改
(3) 普通成员:只能被普通成员函数读写
(4) statc函数:可以被static函数、const函数、普通函数调用
(5) const函数:可以被const函数、成员函数调用
(6) 成员函数:只能被成员函数调用
作用不同:
const:为只读,通常在申明常量中使用
static:局部变量为静态存储方式,调用的初始值为上一次调用值,调用结束后不释放。 全局变量static,以文件为作用域,只有在当前文件中可见。
(1)类中static成员属于类而不属于类对象,const成员属于对象。
(2)类中static成员在类中申明而不能在类中初始化,在类外以 类名::初始化。
(3)类中const成员申明在类中,初始化在构造函数列表中。
(conststatic同时修饰时可以在类中初始化)
19、malloc/free 与new/delete的区别
他们的功能都是动态的申请内存与释放内存。
1、malloc/free是C中的内存申请函数,new/delete是C++中的运算符。
2、new中除了申请内存,还包括了执行对象的构造函数,delete除了释放内存还需要先执行对象的析构函数。
3、对于外部数据对象而言使用malloc/free完全不够,因此C++需要new/delete,new与delete中内部也都是用malloc与free。
20、宏定义与const/inline/enum区别
内联函数与宏定义区别:
内联函数是为了加快函数运行,在编译期间直接把内联函数嵌入目标代码中。是一种牺牲空间换执行速度的做法。这与宏定义相同。与宏定义相比,内联函数有以下优点:
(1) 内联函数有参数类型检查;
(2) 内联函数可以在调试工具中调试;
宏定义只是简单的替换,没有类型检查安全性测试,容易产生边际效应。应该尽可能的使用内联函数代替宏定义函数。
const与宏定义区别:
const和宏都可以用来定义数据常量,const相对于红有许多优点:
(1) const定义的常量有数据类型,而宏没有数据类型。编译会对有数据类型的进行类型安全性检查,宏却只是简单的字符替换,容易出现错误,产生边际效应;(乘法)
(2) const可以被调试工具进行调试,而宏定义不能够被调试;
因此,在C++中应该尽量使用const常量替换的宏定义。
21、什么是迷途指针,与空指针有何区别?
(1)指针所指对象被释放之后就叫迷途指针。空指针是指针值为0(null)的指针。
(2)对空指针再次删除是安全的,如果对迷途指针再次删除,程序会变的非常不稳定,任何情况都有可能发生。
(3)使用迷途指针与空指针都是非法的,而且可能造成程序崩溃,但迷途指针的错误是不可预知的,而空指针的错误是可预知的错误。
因此:指针在释放掉所指内存的实例后,把迷途指针赋0,转换为空指针。
22、定义并使用一个类,编译器默认会为你做那些事?
1)默认提供构造函数与析构函数,当然在需要的情况下,编译器也会提供拷贝构造函数(浅拷贝)以及赋值函数。
23、sizeof与strlen的区别
1)sizeof是C中的运算符,而strlen是一个函数;
2)sizeof计算对象占用的空间大小,可以是字符串也可以是其他的内部数据类型,还可以是自定义的数据结构;strlen是一个函数,只能用于计算字符串的长度,并且遇到\0即停止。
3)sizeof是在编译阶段计算,而strlen是在运行过程中计算。
4)sizeof还可以计算数据类型的大小。
24、你所熟知的模板有哪些?
八种:vector、list、stack、quene、deque、map、multimap、array,其中vector最常被使用。
vector:
1)vector结构是物理地址连续存储,可以修改其空间大小。
2)如果超出了vector当前的大小,内部会分配更大的连续空间,并把原来的数据拷贝过去,然后释放掉原空间。
3)vector可以通过下标访问内部数据,即可用[]操作符访问,因此可以快速随机访问元素。
4)能够快速的在末尾插入新元素,,但是在序列中间插入以及删除元素速度较慢。
list: 1)list每个元素用双向链表相连,可以随机访问元素,但速度不如vector;
2)list插入删除元素比较自由,随机插入比vector要快;
3) list对每个元素分配空间,不存在vector预先分配空间,以及使用中空间不够,需重新分配的情况。
deque:
1)、是逻辑上连续的内存,因此支持下标访问。物理地址是由一段段固定大小连续的块组成;采用一小块连续的内存索引缓存节点,当索引空间满后,会申请一块更大的内存空间做索引。
2)、deque支持随机访问,但由于间接索引,速度不及vector。在头部和尾部操作,访问时间是个常量。在中间增加或者删除元素所需时间随元素个数线性变化。
3)、deque内存管理自动完成,不提供内存管理的成员。
set/multiset:底层为红黑树。set内部的元素值自动排序,每个元素值只能出现一次,不能重复。multiset跟set相同,但它允许出项重复元素。
stack:适配器,默认的参数中容器是deque实现的,可以选择容器list,deque,vector实现栈的结构,后进先出。
quene:适配器,可以采用deque或者list作为底层数据结构,不能使用vector(因为没有提供front)。默认底层数据结构是deuqe。
map/multimap:是包含键值对的集合,可以通过键查找到对应的值;map不支持有相同的键值的元素,但multimap支持有相同键值对的元素。
25、你所知道的进程间通信机制有哪些?
管道、命名管道、信号量、信箱、消息队列、共享内存、socket通信
管道:1)用于父子进程间通信。
2)是半双工的,且是阻塞型的,但某些系统支持全双工。
3)原型:intpipe(int fd[2]),数据从父进程流向子进程,关闭父进程fd[0],关闭子进程fd[1],由子进程流向父进程,则关闭父进程fd[1],关闭子进程fd[0]。
命名管道(FIFO):
1)可以用在不相关的进程之间。
2)FIFO是一种文件类型,可以指定读写是否阻塞(O_NOBLOCK)。
3)主要用途 shell命令使用FIFO从一条管道传递数据到另一条管道,不需要创建中间零时文件。
4)客户进程与服务器进程应用中,FIFO传递数据。
管道和命名管道需要读端准备读,写端写入数据才有意义。
管道与命名管道是字节流模型,没有消息边界,也没有与每个消息关联的类型。
信号量:
1)是一个计数器,用于为多个进程(或线程)提供对共享数据的访问。
2)为了获取共享资源,需要测试该信号量,如果信号量为正值,进程可以使用共享资源,且信号量减1;否则,信号量的值为0,进程进入睡眠状态,直到信号量的值大于0进程被唤醒,从新测试信号量。
消息队列:
1)消息队列是一个消息链表,有足够写的权限可以往队列中放消息,有足够读的权限可以从队列中取消息。
2)与管道不一样,消息队列具有随内核持续性,即进程写入消息后终止,写入的消息不会消亡。
3)消息队列中都具有优先级,消息的数据部分可以为0。
4)消息队列的头消息包含两个属性:允许的最大消息数,每个消息的最大大小。
5)消息队列的函数原型有:mq_open、mq_close、mq_link、mq_sent、mq_receive。
6)消息队列可以通过mq_notify建立异步事件通知,用以告知何时有一个消息放置到了某个空的消息队列中。
消息队列的优点:
1)消息具有随内核持续性,独立于发送和接受进程。
2)消息队列可以设置异步事件通知,可以避免命名管道的阻塞及同步问题,而不用进程提供同步方法。
3)消息队列可以自主的选择可接受消息的类型,而不像管道只能被动接受。
26、线程中互斥与同步机制有哪些?
两个相交线程及进程间两种关系,同步与互斥。其实现依赖于互斥锁、读写锁、条件变量
互斥锁:
1)强调的是资源的访问互斥,即多个线程或进程共享相同资源,而同一时刻只能让其中一个线程或进程占用资源。通过锁定代码段,同一时刻只让一个线程执行,实际上是为了保护数据。
2)互斥锁只有两种状态,即上锁与未上锁。
3)互斥锁有pthread_metex_init、pthread_metex_lock、pthread_metex_unlock、pthread_mutex_trylock等操作。pthread_mutex_trylock是非阻塞调用模式,如果被占用返回忙状态(EBUSY)。
读写锁:
1)与互斥锁功能相同,但读写锁有打开、上读锁、上写锁三种状态,在打开状态可以被线程上读锁也可以上写锁,在已被线程上读锁情况下也可以被其他线程读,但不能写;被线程上写锁后就不可被其他线程读写。
2)也被叫做共享-独占锁,在以读模式加锁时共享,以写模式加锁时独占。
3)如果读写锁已被线程以读模式加锁,随后若有线程希望以写模式加锁,会阻塞之后到来的所有线程。
4)由于效率不及互斥锁,使用于读数据远远大于写数据次数。
条件变量:
1)条件变量与互斥锁结合使用,用来实现线程同步的目的。
2)使用pthread_cond_wait、pthread_cond_signal用来实现线程间同步。
2)条件变量以允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足。
27、信号量理解:
1)信号量分为两种,有名信号量与基于内存共享的信号量。
2)有名信号量可以用于进程间通信也可以用于线程间通信,存放在各个进程的共享存储区。
3)基于内存的信号量分两种,在进程间同步,shared>0,信号量存放在各个进程共享的内存区;shared=0,用于一个进程中的多线程同步,信号量存放在进程中共享存储区。
4)信号量具有随内核持续性,但基于内存的信号量用于单个进程内的多个线程同步,shared=0时,那么具有随进程持续性。
5)信号量的基本操作有sem_open(有名信号量)、sem_init(无名信号量)、sem_wait、sem_post、sem_close、sem_unlink、sem_trywait。
6)等待是执行 while(sem<=0) ; sem--; 等待信号量大于0,并对其减1;挂出post是将信号量加1。
7)信号量有二值信号,可用于临界资源互斥。
28、编译过程:预处理 汇编 编译 连接
1) gcc -o file.c -o file.out : 预处理->汇编->编译并连接成可执行文件
2) gcc -E fille.c 预处理输出结果file.i
3) gcc -s file.s 将预处理结果汇编
4) gcc -c file.out 将汇编结果编译出file.o
5)gcc file.o -o file连接可执行文件file
信号量与条件变量的区别:
1. 互斥锁必须是谁上锁就由谁来解锁,而信号量的wait和post操作不必由同一个线程执行。
2. 互斥锁要么被锁住,要么被解开,和二值信号量类似
3. sem_post是各种同步技巧中,唯一一个能在信号处理程序中安全调用的函数
4. 互斥锁是为上锁而优化的;条件变量是为等待而优化的; 信号量既可用于上锁,也可用于等待,因此会有更多的开销和更高的复杂性
5. 互斥锁,条件变量都只用于同一个进程的各线程间,而信号量(有名信号量)可用于不同进程间的同步。当信号量用于进程间同步时,要求信号量建立在共享内存区。
6. 信号量有计数值,每次信号量post操作都会被记录,而条件变量在发送信号时,如果没有线程在等待该条件变量,那么信号将丢失。
26、进程与线程的关系?
进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。
每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
进程是系统进行资源分配和调度的一个独立单位
线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位
线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。
一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行
堆: 是大家共有的空间,分全局堆和局部堆。全局堆就是所有没有分配的空间,局部堆就是用户分配的空间。堆在操作系统对进程初始化的时候分配,运行过程中也可以向系统要额外的堆,但是记得用完了要还给操作系统,要不然就是内存泄漏。
栈:是个线程独有的,保存其运行状态和局部自动变量的。栈在线程开始的时候初始化,每个线程的栈互相独立,因此,栈是 thread safe的。每个C++对象的数据成员也存在在栈中,每个函数都有自己的栈,栈被用来在函数之间传递参数。操作系统在切换线程的时候会自动的切换栈,就是切换 SS/ESP寄存器。栈空间不需要在高级语言里面显式的分配和释放。
共享内存:
消息队列的布局:
附加操作:
1、稳定排序与不稳定排序
稳定性:在排序中若存在多个相同记录,经过排序后这些具有相同记录的排序次序保持不变即为稳定排序,否则为不稳定排序。
稳定排序: 冒泡排序(O(n2)) 插入排序(O(n2)) 桶排序(O(n))基数排序(O(d*n))
二叉树排序(O(n log n)) 合并排序(O(nlog n))
不稳定排序:
快速排序(O(n log n)) 希尔排序(O(nlog n)) 堆排序(O(n log n))
2、消费者与生产者
3、虚继承以及RTTI type_info (dynamic_cast) 动态类信息的实现