-
C++虚函数:
多态: 静态多态(重载)、动态多态(虚函数)
虚函数
虚函数表:编译器为每个类创建了一个虚函数表,类的每个对象共享一个虚函数表,对象的首地址存放了vptr
-
new/delete 和 malloc/free 的区别:
-
B树和B+树:
B和B+树的区别在于,B+树的非叶子结点只包含导航信息,不包含实际的值,所有的叶子结点和相连的节点使用链表相连,便于区间查找和遍历 B+ 树的优点在于:
由于B+树在内部节点上不包含数据信息,因此在内存页中能够存放更多的key。 数据存放的更加紧密,具有更好的空间局部性。因此访问叶子节点上关联的数据也具有更好的缓存命中率。
B+树的叶子结点都是相链的,因此对整棵树的便利只需要一次线性遍历叶子结点即可。而且由于数据顺序排列并且相连,所以便于区间查找和搜索。而B树则需要进行每一层的递归遍历。相邻的元素可能在内存中不相邻,所以缓存命中性没有B+树好。
B树的优点: 由于B树的每一个节点都包含key和value,因此经常访问的元素可能离根节点更近,因此访问也更迅速。
数据库索引采用B+树的主要原因是B树在提高了磁盘IO性能的同时并没有解决元素遍历的效率低下的问题。正是为了解决这个问题,B+树应运而生。B+树只要遍历叶子节点就可以实现整棵树的遍历。而且在数据库中基于范围的查询是非常频繁的,而B树不支持这样的操作(或者说效率太低)。
栈作用可以从两个方面体现:函数调用 和 多任务支持 。
hash_set/hash_map:
底层是有hash_table所封装实现的,hash_table(开链法解决冲突)的底层是由vector实现的,set/map底层是由RB tree实现的。
-
进程和线程的区别
线程是程序执行的最小单位,而进程是操作系统分配资源的最小单位;
一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线
进程之间相互独立,但同一进程下的各个线程之间共享程序的内存空间(包括代码段,数据集,堆等)及一些进程级的资源(如打开文件和信 号等),某进程内的线程在其他进程不可见;
调度和切换:线程上下文切换比进程上下文切换要快得多
地址空间:同一进程的线程共享本进程的地址空间,而进程之间则是独立的地址空间。
资源拥有:同一进程内的线程共享本进程的资源如内存、I/O、cpu等,但是进程之间的资源是独立的。
进程独占的资源:地址空间、全局变量、打开的文件、子进程、信号量、账户信息
线程占有的资源:栈、寄存器、状态、程序计数器
- 一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。所以多进程要比多线程健壮。
- 进程切换时,消耗的资源大,效率高。所以涉及到频繁的切换时,使用线程要好于进程。同样如果要求同时进行并且又要共享某些变量的并发操作,只能用线程不能用进程
执行过程:每个独立的进程程有一个程序运行的入口、顺序执行序列和程序入口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
线程是处理器调度的基本单位,但是进程不是。 两者均可并发执行。
优缺点:
线程执行开销小,但是不利于资源的管理和保护。线程适合在SMP机器(双CPU系统)上运行。 进程执行开销大,但是能够很好的进行资源管理和保护。进程可以跨机器前移。
- 何时使用多进程,何时使用多线程?
对资源的管理和保护要求高,不限制开销和效率时,使用多进程。
要求效率高,频繁切换时,资源的保护管理要求不是很高时,使用多线程。
多进程 多进程通信:匿名管道(pipe),具名管道(FIFO)、消息队列、共享内存(单机内)、信号、sockets(跨主机)等 多进程同步:互斥器、条件变量、读写锁、文件锁、信号量
-
指针和引用(引用的本质是一个指针常量,所以引用指向是指针)的区别:
引⽤只能在定义时初始化⼀次,之后不能改变指向其它变量(从⼀⽽终);指 针变量的值可变。
引⽤必须指向有效的变量,指针可以为空。
sizeof指针对象和引⽤对象的意义不⼀样。 sizeof引⽤得到的是所指向的变量的 ⼤⼩,⽽sizeof指针是对象地址的⼤⼩。
指针和引⽤⾃增(++)⾃减(--)意义不⼀样。
相对⽽⾔,引⽤⽐指针更安全。
-
内存管理:
-
外部碎片->伙伴算法
伙伴算法(Buddy system)把所有的空闲页框分为11个块链表,每块链表中分布包含特定的连续页框地址空间:伙伴算法每次只能分配2的幂次页的空间,比如一次分配1页,2页,4页,8页,…,1024页(2^10)等等,每页大小一般为4K,因此,伙伴算法最多一次能够分配4M的内存空间。 申请和回收过程:
-
内部碎片->slab分配器
slab根据对象进行管理的,将内核中经常使用的对象 放到高速缓存中。高速缓存链表cache_chain的每一个高速缓存都是由kmem_cache结构来描述,每个高速缓存都包含了一个slab列表,分别是: slabs_full(完全分配的 slab)、slabs_partial(部分分配的 slab)和slabs_empty(空 slab,或者没有对象被分配) slab是slab分配器的最小单位,单个slab可以在slab链表之间移动,例如如果一个半满slab被分配了对象后变满了,就要从slabs_partial中被删除,同时插入到slabs_full中去。
slab 分配器:slab根据对象进行管理的,当申请一个对象时,从slab列表中寻找一个大小合适的单元出去,释放时,将其重新保存在该列表中,从而避免内部碎片。
-
redis
五种数据类型string,hash,set,list,zset. zset的底层实现(跳跃表+字典) redis的两种持久化方式 实现分布式锁set nx ex -
REDIS持久化:
RDB持久化: 将某个时间点的所有数据都存放到硬盘上,将快照复制到其他服务器上,从而创建具有相同的副本,如果系统故障,将丢失最后一次创建快照之前的数据。如果数据量很大,保存快照的时间会很长。
-
AOF持久化:添加同步选项,确保写命令啥时候同步到磁盘文件上,对文件的写入不会立马同步到磁盘,先存储到缓冲区,由os决定何时同步到磁盘。有以下同步选项:
always 选项会严重减低服务器的性能
everysec 选项比较合适,可以保证系统崩溃时只会丢失一秒左右的数据,并且 Redis 每秒执行一次同步对服务器性能几乎没有任何影响
no 选项并不能给服务器性能带来多大的提升,而且也会增加系统崩溃时数据丢失的数量。
随着服务器写请求的增多,AOF 文件会越来越大。Redis 提供了一种将 AOF 重写的特性,能够去除 AOF 文件中的冗余写命令。
-
菱形继承:B、C继承自A,D继承B、C
问题:可能在用D初始化基类A时,会出现基类A不明确的情况,避免使用这种结构
-
解决方法:使用虚拟继承,base class A不管在继承串链中被继承多少次,永远只会存在一个实例。
[图片上传中...(image-9dffa7-1565320800343-1)]
[图片上传中...(image-bc84df-1565320800343-0)]
B、C类不再保存A的具体内容,而是保存了一份偏移地址,D调用a时,调用的就是class A的a,但是对于B、C相同的变量名,D在调用时还要利用限定域来处理(D.B::/D.C::)
-
高并发限流:
一般开发高并发系统常见的限流有:限制总并发数(比如数据库连接池、线程池)、限制瞬时并发数(如nginx的limit_conn模块,用来限制瞬时并发连接数)、限制时间窗口内的平均速率(如Guava的RateLimiter、nginx的limit_req模块,限制每秒的平均速率);
限流算法
常见的限流算法有:令牌桶(允许一定程度的突发量)、漏桶(平滑突发流入速率)。计数器(限制总并发数)也可以进行粗暴限流实现。
-
&& 右值引用和左值引用的区别
绑定的对象(引用的对象)不同,左值引用绑定的是返回左值引用的函数、赋值、下标、解引用、前置递增递减
左值持久,右值短暂,右值只能绑定到临时对象,所引用的对象将要销毁或该对象没有其他用户
使用右值引用的代码可以自由的接管所引用对象的内容 右值引用应用:移动赋值函数和移动构造函数,减少了拷贝赋值和拷贝构造的开销。
Const引用
僵尸进程:
父进程未回收(wait/waitpid)退出到的子进程,子进程退出时仍然保存了一定的信息(进程ID,退出状态,运行时间),这样,大量的进程号被占用。
孤儿进程:
父进程退出了,它的子进程还在运行,这些子进程将由init进程回收。
守护进程:
运行在后台的特殊进程。独立于终端并且周期性地执行某种任务或等待处理的时间。Linux下的大多数服务器都是利用守护进程实现的
-
守护进程的创建:
umask:umask是从权限中“拿走”相应的位,且文件创建时不能赋予执行权限。文件和目录创建缺省权限是666和777(r:4 w:2 x:1),减掉umask的值便是创建文件或目录时的权限。
int dup(int oldfd); 用来复制参数oldfd所指的文件描述符。当复制成功是,返回最小的尚未被使用过的文件描述符
int dup2(int oldfd, int newfd):可以指定复制到的文件描述符
信号
sigaction:功能是检查或修改与指定信号相关联的处理动作(可设置回调来触发相关处理动作)
SIGCHLD:当子进程退出时,会向父进程发送SIGCHLD信号,而父进程就是根据是否收到这个信号来判断子进程是否退出的
wait:阻塞式等待
waitpid:非阻塞式等待
PS命令:进程的快照
内存泄漏会导致内存溢出
类模板偏特化:
特例化本质上是我们顶替了编译器的工作,我们帮编译器做了类型推导
-
空间配置器:
SGI设置了两层的配置器,第一级和第二级配置器。
第一级配置器 直接调用malloc和free来配置和释放内存,如果配置区块大于128bytes,会调用第一级配置器
第二级配置器 (内存池) 配置区块小于等于128bytes时,使用第二级配置器。第二季配置器采用自由链表+内存池进行管理。自由链表类似于hash桶,它的大小为16,每个位置代表区别大小,范围从8bytes-128bytes,其中大小都为8bytes的倍数。如果用户需要是一块n字节的区块,且n <= 128(调用第二级配置器),此时Refill填充是这样的:(需要注意的是:系统会自动将n字节扩展到8的倍数也就是RoundUP(n),再将RoundUP(n)传给Refill)。用户需要n块,且自由链表中没有,因此系统会向内存池申请20 * n大小的内存块,如果内存池大于 20 * n,那么直接从内存池中取出。如果内存池小于20 * n,但是比一块大小n要大,那么此时将内存最大可分配的块数给自由链表,并且更新最大分配块数x (x < 20)。如果内存池连一个区块的大小n都无法提供,那么首先先将内存池残余的零头给挂在自由链表上,然后向系统heap申请空间,申请成功则返回,申请失败则到自己的自由链表中看看还有没有可用区块返回,如果连自由链表都没了最后会调用一级配置器。 两层空间配置器解决了以下问题:
频繁使用malloc,free开辟释放小块内存带来的性能效率的低下
内存碎片问题,导致不连续内存不可用的浪费 带来了一些问题:
内存碎片的问题,自由链表所挂区块都是8的整数倍,因此当我们需要非8倍数的区块,往往会导致浪费,比如我只要1字节的大小,但是自由链表最低分配8块,也就是浪费了7字节,我以为这也就是通常的以空间换时间的做法,这一点在计算机科学中很常见。
我们发现似乎没有释放自由链表所挂区块的函数?确实是的,由于配置器的所有方法,成员都是静态的,那么他们就是存放在静态区。释放时机就是程序结束,这样子会导致自由链表一直占用内存,自己进程可以用,其他进程却用不了。
-
malloc的底层实现
brk()、sbrk():两者的作用是扩展heap的上界brk 。
内存分配器中,为了解决多线程锁争夺问题,分为主分配区main_area和非主分配区no_main_area
ptmalloc在实现中定义chunk结构来描述这些块。
malloc将相似大小的chunk用双向链表链接起来,这样的一个链表成为bin,ptmalloc一共维护了128个bin。
基于chunk的大小有以下几种bin:
Fast bin :当用户释放一块不大于max_fast(默认值64B)的chunk的时候,会默认会被放到fast bins上。当需要给用户分配的 chunk 小于或等于 max_fast 时,malloc 首先会到fast bins上寻找是否有合适的chunk,
Unsorted bin :当用户释放的内存大于max_fast或者fast bins合并后的chunk都会首先进入unsorted bin上。
Small bin
Large bin
- 初始化:
ptmalloc 在开始时,若请求的空间小于 mmap 分配阈值(mmap threshold,默认值为 128KB)时,主分配区会调用 sbrk()增加一块大小为 (128 KB + chunk_size) align 4KB 的空间作为 heap。非主分配区会调用 mmap 映射一块大小为 HEAP_MAX_SIZE(32 位系统上默认为 1MB,64 位系统上默认为 64MB)的空间作为 sub-heap。
-
内存分配malloc流程 简而言之: 获取分配区(arena)并加锁–> fast bin –> unsorted bin –> small bin –> large bin –> top chunk –> 扩展堆
-
内存回收流程
-
内存分配的几种方式:
从静态存储区分配:此时的内存在程序编译的时候已经分配好,在程序的整个运行期间都存在。全局变量和static变量都在这里存储
栈区分配:相关代码执行时创建,执行结束时被自动释放。效率高,但是容量有限
堆区分配:动态内存分配
-
UDP面向报文,TCP面向字节:
- UDP是面向报文的,发送方的UDP对应用层交下来的报文,不合并,不拆分,只是在其上面加上首部后就交给了下面的网络层,也就是说无论应用层交给UDP多长的报文,它统统发送,一次发送一个。而对接收方,接到后直接去除首部,交给上面的应用层就完成任务了。因此,它需要应用层控制报文的大小。 2.TCP是面向字节流的,它把上面应用层交下来的数据看成无结构的字节流来发送,可以想象成流水形式的,发送方TCP会将数据放入“蓄水池”(缓存区),等到可以发送的时候就发送,不能发送就等着,TCP会根据当前网络的拥塞状态来确定每个报文段的大小。
explicit:防止内置类型隐式转换()
-
为什么不要在构造函数和析构函数中调用虚函数?(讨论基于简单的单继承)
在运行构造函数或者析构函数时,对象都是不完整的。
不要在构造函数和析构函数中调用虚函数,因为这种情况下的虚函数调用不会调用到外层派生类的虚函数
对象的虚函数表地址在对象的构造和析构过程中会随着部分类的构造和析构而发生变化,这一点应该是编译器实现相关的。
-
fork()
fork()之后,子进程(基于copy-on-write机制)会继承父进程的地址空间和文件描述符,其他资源像如下都不会被继承:
父进程的文件锁
父进程的某些定时器
父进程的内存锁
原因: 子进程的创建是为了协助父进程完成相关任务,仅此而已。与此目的不相关的资源,子进程没有必要继承,继承了自会白白浪费内存资源。
-
多线程与fork()
linux的fork()只克隆当前线程的thread of control ,不克隆其他线程,fork()之后,除了当前线程外,其他线程都消失了,不能一下子fork出一个和父进程一样的多线程子进程。
-
死锁
避免死锁:
加锁顺序(线程按照一定的顺序加锁)
加锁时限(线程尝试获取锁的时候加上一定的时限,超过时限则放弃对该锁的请求,并释放自己占有的锁)
死锁检测 另外,我在项目中使用RAII机制封装了锁,一定程度上避免了死锁;
创建一个线程默认的状态是joinable, 如果一个线程结束运行但没有被join,则它的状态类似于进程中的Zombie Process,即还有一部分资源没有被回收(退出状态码),所以创建线程者应该调用pthread_join来等待线程运行结束,并可得到线程的退出代码,回收其资源(类似于wait,waitpid) 但是调用pthread_join(pthread_id)后,如果该线程没有运行结束,调用者会被阻塞,在有些情况下我们并不希望如此,比如在Web服务器中当主线程为每个新来的链接创建一个子线程进行处理的时候,主线程并不希望因为调用pthread_join而阻塞(因为还要继续处理之后到来的链接),这时可以在子线程中加入代码 pthread_detach(pthread_self()) 或者父线程调用 pthread_detach(thread_id)(非阻塞,可立即返回) 这将该子线程的状态设置为detached,则该线程运行结束后会自动释放所有资源。
-
线程池
项目中封装了IO线程(EventLoopThread),IO线程调用threadFunc创建EventLoop对象,通知主线程已经创建完成。用一个main reactor创建EventLoopThreadPoo,在线程池(EventLoopThreadPool)中,创建初初始化了一个线程队列和任务队列。在线程池中将EventLoop(任务)和Thread(线程)绑定,将IO线程中调用线程函数返回的EventLoop对象封装到EventLoop池中,使用轮询的方式返回每个EventLoop。
-
模板偏特化:
-
C++ 11
-
Linux常用命令
df:磁盘使用情况 lsof -i port:查看端口占用(root权限) netstat -tunlp |grep 8000:查看8000端口占用的情况 alias:设置指令的别名 kill -9 25260 # 表示杀死25260进程(杀死占用指定端口的进程)
-
索引节点inode
硬链接: 这意味着,可以用不同的文件名访问同样的内容;对文件内容进行修改,会影响到所有文件名;但是,删除一个文件名,不影响另一个文件名的访问。这种情况就被称为"硬链接"(hard link)。
硬链接造成的:
Unix/Linux系统允许,多个文件名指向同一个inode号码
每个dentry(目录项)都有一个唯一的inode,而每个inode则可能有多个dentry
软链接又叫符号链接,这个文件包含了另一个文件的路径名。可以是任意文件或目录,可以链接不同文件系统的文件。
-
TCP套接字端口复用SO_REUSEADDR
端口复用允许在一个应用程序可以把 n 个套接字绑在一个端口上而不出错。同时,这 n 个套接字发送信息都正常,没有问题。但是,这些套接字并不是所有都能读取信息,只有最后一个套接字会正常接收数据。
-
socket在什么情况下可读?
接收缓冲区有数据,一定可读
对方正常关闭socket,也是可读
对于侦听socket,有新连接到达也可读=
socket有错误发生,可读也可写
-
设计一个洗牌的算法,并说出算法的时间复杂度
- 第一种:// 凑合,但不是真正随机
for i:=1 to n do swap(a[i], a[random(1,n)]);
- 第二种:// 真正的随机算法 其中,random(a,b)函数用于返回一个从a到b(包括a和b)的随机整数。
for i:=1 to n do swap(a[i], a[random(i,n)]);
算法复杂度是O(n。。。),要研究下random的实现。
-
路由表示做什么用的?
路由表是用来决定如何将包从一个子网传送到另一个子网的,换局话说就是用来决定从一个网卡接收到的包应该送的哪一张网卡上的。
-
路由器和交换机
交换机工作于链路层,用于隔离冲突域,连接的所有设备属于同一个子网,负责子网内部通信
路由器工作于网络层,用于隔离子网,连接的设备分属于不同的子网,工作范围是多个子网之间,负责网络和网络之间的通信
-
默认网关指的是路由表中的缺省路由
它们的主要工作如下:
路由器:寻址,转发(依靠 IP 地址) 交换机:过滤,转发(依靠 MAC 地址)
我们可以看出这两者的主要工作就是转发数据,但是不同之处是,依靠的地址不同,这是一个根本区别!
路由器内有一份路由表,里面有它的寻址信息(就像是一张地图),它收到网络层的数据报后,会根据路由表和选路算法将数据报转发到下一站(可能是路由器、交换机、目的主机)
交换机内有一张MAC表,里面存放着和它相连的所有设备的MAC地址,它会根据收到的数据帧的首部信息内的目的MAC地址在自己的表中查找,如果有就转发,如果没有就放弃
类成员函数的地址
只用一段空间来存放这个共同的函数代码段,在调用各对象的函数时,都去调用这个公用的函数代码。 显然,这样做会大大节约存储空间。C++编译系统正是这样做的,因此每个对象所占用的存储空间只是该对象的数据部分(虚函数指针和虚基类指针也属于数据部分)所占用的存储空间,而不包括函数代码所占用的存储空间
不允许空值引用
不存在指向空值的引用这个事实意味着使用引用的代码效率比使用指针的要高,因为在使用引用之前不需要测试它的合法性。
链表顺序访问和数组顺序访问哪个快?
相较于链表,数组的地址在物理上连续的,所以CPU可以为其建立cpu缓存加速访问,然而链表就需要到去内存中读取。
mysql优化数据访问
-
减少请求数据量
只返回必要的列:不要使用select *
只返回必要的行:使用LIMIT语句限制
缓存重复查询的数据
-
减少服务器端扫描的行数
使用索引来覆盖查询
-
智能指针
std::auto_ptr 可用来管理单个对象的对内存,但是,请注意如下几点:
尽量不要使用“operator=”。如果使用了,请不要再使用先前对象。
记住 release() 函数不会释放对象,仅仅归还所有权。
std::auto_ptr 最好不要当成参数传递(读者可以自行写代码确定为什么不能)。
由于 std::auto_ptr 的“operator=”问题,有其管理的对象不能放入 std::vector等容器中。
extern关键字
**extern一般是使用在多文件之间需要共享某些代码 extern "C":简而言之,为C++调用C代码提供支持,因为C语言不支持重载, 保证在extern "C"声明的函数按照C来编译
程序的执行过程
- 预编译:
主要处理源代码中的预处理指令,引入头文件,去除注释,处理所有的条件编译指令,宏的替换,添加行号,保留所有的编译器指令。
- 编译
编译过程所进行的是对预处理后的文件进行语法分析,词法分析,语义分析,符号汇总,然后生成汇编代码。
- 汇编
将汇编代码转成二进制文件,二进制文件就可以让机器来读取。每一条汇编语句都会产生一句机器语言。
- 链接
将目标文件、启动代码、库文件链接成可执行文件的过程,这个文件可被加载或拷贝到存储器执行。
静态链接
动态链接
惊群问题
多进程或者多线程阻塞在一个事件上(如accept、epoll) accept:
其实在Linux2.6版本以后,内核内核已经解决了accept()函数的“惊群”问题,大概的处理方式就是,当内核接收到一个客户连接后,只会唤醒等待队列上的第一个进程或线程。所以,如果服务器采用accept阻塞调用方式,在最新的Linux系统上,已经没有“惊群”的问题了。
epoll:
Nginx中使用mutex互斥锁解决这个问题,具体措施有使用全局互斥锁,每个子进程在epoll_wait()之前先去申请锁,申请到则继续处理,获取不到则等待,并设置了一个负载均衡的算法(当某一个子进程的任务量达到总设置量的7/8时,则不会再尝试去申请锁)来均衡各个进程的任务量
项目中无惊群问题,无论是accept(主线程)还是epoll(每个线程)都只有一个线程阻塞在其上。
static
static全局函数
可以访问全局函数、static全局函数、全局变量
static全局变量
static成员变量、成员函数
使用static修饰的函数,除了可以访问static修饰的变量及函数,还有template模板里面定义的默认值:
template<typename T, int i=1>
class someComputing {
public:
typedef volatile T* retType; // 类型计算
enum { retValume = i + someComputing<T, i-1>::retValume }; // 数值计算,递归
static void f() { std::cout << "someComputing: i=" << i << '\n'; }
};
多重继承
mmap
定义: 同一块物理内存被映射到两个进程的各自的进程地址空间.一个进程可以及时看到另一个进程对共享内存的更新,反之亦然.
优点:只有两次数据复制
采用共享内存通信的一个显而易见的好处效率高,因为进程可以直接读写内存,而不需要任何数据的复制.对于向管道和消息队列等通信等方式,则需要在内核和用户空间进行四次的数据复制,而共享内存则只需要两次数据复制:一次从输入文件到共享内存区,另一个从共享内存区到输出文件.
mmap系统调用是的是的进程间通过映射同一个普通文件实现共享内存.普通文件被映射到进程地址空间后,进程可以像访问普通内存一样对文件进行访问,不必再调用read,write等操作.与mmap系统调用配合使用的系统调用还有munmap,msync等.
define 、const区别
define定义的常量不带类型,const带类型
define在编译的预处理阶段就起作用,const在编译运行的时候起作用
define是简单的字符串替换,没有类型检查
define定义的常量占用代码段,const占用数据段
一个32位的操作系统能同时运行多少个程序(进程)?
若物理地址也是32位,则内存最大位4GB,现代操作系统使用内存分页,常见体系结构一个页至少是4KB,一个程序至少使用一个page才能实现地址空间隔离,这么计算=》内存方面最多允许同时运行2^20个程序(进程)
在一个32位的操作系统下,一个进程可以开启多少个线程?
???一个进程默认是2G,而每个线程默认有1M的栈共建,理论上线程数最多是2048个。
内核空间
32位的系统,内核空间占1GB,高位0xC0000000到0xFFFFFFFF的1GB线性地址空间。 内核线性地址空间由所有进程共享,只有在进程陷入到内核态的时候,可以访问内核空间
可重入和线程安全
-
可重入:
一个可重入的函数简单来说就是可以被中断的函数,也就是说,可以在这个函数执行的任何时刻中断它,转入OS调度下去执行另外一段代码,而返回控制时不会出现什么错误
-
特点:
函数被不同的流调用,有可能会出现第一次调用还没返回时就再次进入该函数开始下一次调用。
因此,线程安全函数不一定的是可重入函数,因为阻塞在共享区,无法进入一次新的函数调用。
可重入函数的条件:
1)在写函数时候尽量使用局部变量(例如寄存器、堆栈中的变量);
2)对于要使用的全局变量要加以保护(如采取关中断、信号量等互斥方法),这样构成的函数就一定是一个可重入的函数。
指针变量
得看定义的地方,若是函数中,指针变量分配在栈中,随着函数结束而销毁,但是指针变量的指向不会。
nullptr(C++11特性)
引入的目的:用来区分0和NULL
因为重载以下函数会出问题,NULL会被视为int类型
在不同的编译器上,有的会将NULL宏定义为0,有的会定义为
(void*(0))
void fun(int);
void fun(void *);
fun(NULL); //会调用void fun(int)
bss段存放未初始化的全局变量
data段(全局静态区):存放初始化的全局变量、常量、静态变量(无论初始化与否)
通过编译生成的可执行文件,可以判断