9.13信锐面经
1.C++程序的编译过程?C++头文件是怎么预处理的?
当编译器遇到#include指令时,它会将指定的头文件内容插入到当前源文件中。这个过程是递归的,即如果被包含的头文件中又有其他的#include指令,那么也会继续包含相应的头文件。
头文件中可能包含宏定义,编译器会在预处理阶段进行宏展开。
头文件中可以使用条件编译指令来控制哪些代码被编译。
总的来说,C++ 头文件的预处理是在编译之前进行的,它的主要作用是将头文件的内容整合到源文件中,并进行一些文本替换和条件控制,为后续的编译过程做好准备。
2.知道什么是条件编译吗?作用是什么?
3.如何防止头文件被重复引用?什么情况下重复引用会出现问题?
在一个项目中,重复包含两个或以上头文件就会出现问题。
4.什么原因不允许这样重复引用呢?
5.Linux如何去终止一个进程?
回答了ctrl+c
6.为什么Ctrl+c可以终止进程?Ctrl+c一定会终止这个进程吗?
1)当在终端中按下 Ctrl+C 时,实际上是向正在运行的前台进程发送一个中断信号(SIGINT)。操作系统会将这个信号传递给目标进程,进程在接收到这个信号后,通常会采取一些预设的动作来响应。 对于很多程序来说,默认的行为是终止运行。这是因为在程序的设计中,通常会对一些常见的信号进行处理,而 SIGINT 就是其中之一。当程序接收到 SIGINT 信号时,会进行清理工作,如关闭文件、释放资源,然后退出。
2)不一定会,因为如果此时的程序忽略了SIGINT信号,那么就算是按下了Ctrl+c 也不会终止这个进程,例如,一些长期运行的服务器程序可能会选择忽略 SIGINT 信号,以确保在意外情况下不会被轻易终止。在 C++ 中,可以使用signal函数来设置对特定信号的处理方式。如果程序将对 SIGINT 的处理设置为SIG_IGN(忽略信号),那么 Ctrl+C 就不会起作用。
7.如何实现忽略这个信号呢?
忽略信号是让信号失效的三种方式之一,
1)屏蔽阻塞信号 (拒之门外)
2)忽略信号 (忽略掉信号的行为 ,例如工具卸掉了)
3)信号捕捉(让信号为用户所用)
SIGKILL SIGSTP 这两个信号除外
【9.SIGKILL】 直接为内核服务,较高的权限,只要发出必然杀死进程,无法被捕捉忽略等处理
【19.SIGSTP】 直接为内核服务,较高的权限,只要发出必然挂起进程,无法被捕捉忽略等处理
在 Linux 中,可以使用以下几种方法来实现忽略 SIGINT(Ctrl+C)信号:
一、使用signal函数
在 C 或 C++ 程序中,可以使用signal函数来设置对特定信号的处理方式。
在这个例子中,通过signal(SIGINT, SIG_IGN);将对 SIGINT 信号的处理设置为忽略。
二、使用sigaction结构体
sigaction结构体提供了更精细的信号处理控制。这个例子中,通过设置sigaction结构体的成员来指定对 SIGINT 信号的忽略处理。
8.如何去定义一个静态变量?不加static的成员变量和加static的成员变量有什么区别?
可以使用static定义一个静态变量
区别:
一、存储位置和生命周期
不加static的成员变量: 存储在类的每个对象实例中。每个对象都有自己独立的一份非静态成员变量的副本。 生命周期与对象的生命周期相同。当对象被创建时,非静态成员变量被分配内存;当对象被销毁时,非静态成员变量的内存被释放。
加static的成员变量: 存储在静态存储区。不依赖于任何特定的对象实例而存在。 生命周期从程序开始运行时起,到程序结束时止。在程序运行期间,静态成员变量始终存在,即使没有创建任何类的对象。
二、访问方式
不加static的成员变量: 通过类的对象来访问。例如,obj.nonStaticVar,其中obj是类的一个对象,nonStaticVar是非静态成员变量。 也可以在类的成员函数中直接访问,使用this指针来引用当前对象的非静态成员变量。
加static的成员变量: 可以通过类名直接访问,无需创建对象。例如,MyClass::staticVar,其中MyClass是类名,staticVar是静态成员变量。 也可以通过类的对象来访问,但通常推荐使用类名来访问静态成员变量,以明确其静态性质。
三、对象独立性
不加static的成员变量: 每个对象都有自己独立的非静态成员变量副本。修改一个对象的非静态成员变量不会影响其他对象的同名变量。
加static的成员变量: 所有对象共享同一个静态成员变量。对静态成员变量的修改会影响到所有的对象。
9.内存分配的常用方式---new,malloc区别
1、 new/delete是C++关键字,需要编译器支持。malloc/free是库函数,需要头文件支持; 2、 使用new操作符申请内存分配时无须指定内存块的大小,编译器会根据类型信息自行计算。而malloc则需要显式地指出所需内存的尺寸。
3、 new操作符内存分配成功时,返回的是对象类型的指针,类型严格与对象匹配,无须进行类型转换,故new是符合类型安全性的操作符。而malloc内存分配成功则是返回void * ,需要通过强制类型转换将void*指针转换成我们需要的类型。
4、 new内存分配失败时,会抛出bac_alloc异常。malloc分配内存失败时返回NULL。
5、 new会先调用operator new函数,申请足够的内存(通常底层使用malloc实现)。然后调用类型的构造函数,初始化成员变量,最后返回自定义类型指针。delete先调用析构函数,然后调用operator delete函数释放内存(通常底层使用free实现)。malloc/free是库函数,只能动态的申请和释放内存,无法强制要求其做自定义类型对象构造和析构工作。
10.malloc为什么用mmap来进行内存分配
大内存分配的效率考虑
1.避免内存碎片:对于较大尺寸的内存分配请求,使用mmap可以直接从操作系统获取一块连续的虚拟内存区域,而不需要在堆中进行复杂的内存管理操作,从而减少了内存碎片的产生。在传统的堆内存分配中,随着频繁的分配和释放操作,可能会导致内存空间被分割成许多小块,难以满足大尺寸内存请求。
2.快速分配和释放:mmap分配的内存可以在释放时直接交还给操作系统,而不需要像传统堆内存那样进行复杂的合并和整理操作。这使得大内存的分配和释放更加高效。
共享内存:如果需要在多个进程之间共享内存,mmap可以将同一块物理内存映射到不同进程的地址空间中,实现高效的进程间通信。而传统的malloc分配的内存一般只能在单个进程内使用。
内存保护:mmap可以设置特定的内存保护属性,如只读、读写、执行等,这对于一些需要严格控制内存访问权限的场景非常有用。例如,可以将某些关键数据区域设置为只读,以防止意外修改。
11.如何用mmap来实现多个进程共享这块内存区域**
12.什么是内存碎片?
内存碎片是指在计算机内存分配过程中,由于频繁的分配和释放操作,导致内存空间被分割成许多不连续的小块,这些小块不能被有效地利用来满足较大尺寸的内存请求。
内部碎片:指已分配给某个进程或对象的内存区域中未被使用的部分。例如,当一个程序请求一块特定大小的内存时,系统可能会分配一个稍大一些的内存块,以满足对齐要求或提高分配效率。这样,分配的内存块中超出实际需求的部分就形成了内部碎片。
外部碎片:指在内存中存在的一些不连续的、较小的空闲内存块,这些块单独来看可能无法满足一个较大的内存分配请求。外部碎片通常是由于频繁的内存分配和释放操作导致内存空间被分割成许多小的、不连续的部分。
拓展:减少内存碎片的方法
内存池技术:预先分配一块较大的内存区域作为内存池,当需要内存时从内存池中分配,而不是直接向系统申请。这样可以减少频繁的系统调用和内存分配的随机性,从而降低内存碎片的产生。
合适的内存分配算法:选择合适的内存分配算法可以有效地减少内存碎片。例如,伙伴系统算法可以通过合并相邻的空闲内存块来减少外部碎片,而 slab 分配器可以减少内部碎片的产生。
13.为什么会区分内部和外部呢?
一、产生位置和方式不同
内部碎片: 产生于已分配的内存块内部。当进行内存分配时,由于内存分配策略和对齐要求等原因,分配的内存块可能会比实际需求的空间稍大一些。例如,以页为单位进行内存分配时,如果一个进程只需要比一页稍微小一点的内存空间,但仍然被分配了一页,那么这一页中未被使用的部分就是内部碎片。 内部碎片的产生主要是由于内存分配的粒度和实际需求之间的不匹配。通常,内存分配器会按照一定的规则进行分配,而这些规则可能导致分配的内存块大于实际需求。
外部碎片: 存在于已分配的内存块之间。当程序频繁地进行内存分配和释放操作时,内存空间会被不断地分割成许多不连续的小块。这些小块可能单独来看都太小而无法满足一个较大的内存分配请求,从而形成外部碎片。 外部碎片的产生主要是由于内存的动态分配和释放过程中,不同大小的内存请求导致内存空间被分割成不规则的小块,难以有效地合并和利用。
14.线程间是如何进行通信的?
15.进程之间是如何进行通信的?
管道,消息队列,共享内存,信号量,信号,Socket