C++常见面试题整理(一)
1、printf("%s", s)与printf(s)有何区别?
pritnf(s)这种写法是存在严重安全漏洞的,这被称为printf格式化字符串漏洞攻击。这里使用s而不是一个字符串字面常量,而s传入什么内容其实是不可控的,若传入字符中存在%,就会输出栈中其他一些内容。要是s还是可以由外部输入的,那就可以通过巧妙的构造s的形式来实现访问栈中本来没有权限访问的内容,这就是所谓的格式化字符串漏洞攻击。
2、static和const
3、验证CPU字节序是小端格式还是大端格式
使用联合体,因为联合体共享内存空间。
4、cast转换(强制转换)
5、指针与引用的区别
6、智能指针
智能指针的作用是管理一个指针,因为存在以下这种情况:申请的空间在函数结束时忘记释放,造成内存泄漏。智能指针主要用于管理在堆上分配的内存,它将普通的指针封装为一个栈对象。当栈对象的生存周期结束后,会在析构函数中释放掉申请的内存,从而防止内存泄漏。所以智能指针的作用原理就是在函数结束时自动释放内存空间,不需要手动释放内存空间。
(1)auto_ptr(c++98的方案,cpp11已经抛弃)
采用所有权模式。可以剥夺原有auto_ptr的所有权,访问被剥夺所有权的auto_ptr会报错,缺点就是:存在潜在的内存崩溃问题
(2)unique_ptr(替换auto_ptr)
采用所有权模式,unique_ptr实现独占式拥有或严格拥有概念,保证同一时间内只有一个智能指针可以指向该对象。它对于避免资源泄露(例如“以new创建对象后因为发生异常而忘记调用delete”)特别有用。unique_ptr比auto_ptr更安全。
(3)shared_ptr
shared_ptr实现共享式拥有概念。多个智能指针可以指向相同对象,该对象和其相关资源会在“最后一个引用被销毁”时候释放。它使用计数机制来表明资源被几个指针共享。可以通过成员函数use_count()来查看资源的所有者个数。当我们调用release()时,当前指针会释放资源所有权,计数减一。当计数等于0时,资源会被释放。
(4)weak_ptr
weak_ptr 是一种不控制对象生命周期的智能指针, 它指向一个 shared_ptr 管理的对象. 进行该对象的内存管理的是那个强引用的 shared_ptr。weak_ptr只是提供了对管理对象的一个访问手段。weak_ptr 设计的目的是为配合 shared_ptr 而引入的一种智能指针来协助 shared_ptr 工作, 它只可以从一个 shared_ptr 或另一个 weak_ptr 对象构造, 它的构造和析构不会引起引用记数的增加或减少。weak_ptr是用来解决shared_ptr相互引用时的死锁问题,如果说两个shared_ptr相互引用,那么这两个指针的引用计数永远不可能下降为0,资源永远不会释放。它是对对象的一种弱引用,不会增加对象的引用计数,和shared_ptr之间可以相互转化,shared_ptr可以直接赋值给它,它可以通过调用lock函数来获得shared_ptr。但是其可以检测到所管理的对象是否已经被释放,从而避免非法访问。
7、野指针
野指针就是指向一个已删除的对象或者未申请访问受限内存区域的指针。
8、为什么父类的析构函数必须是虚函数?为什么C++默认的析构函数不是虚函数
将可能会被继承的父类的析构函数设置为虚函数,可以保证当我们new一个子类,然后使用基类指针指向该子类对象,释放基类指针时可以释放掉子类的空间,防止内存泄漏。
C++默认的析构函数不是虚函数是因为虚函数需要额外的虚函数表和虚表指针,占用额外的内存。而对于不会被继承的类来说,其析构函数如果是虚函数,就会浪费内存。因此C++默认的析构函数不是虚函数,而是只有当需要当作父类时,设置为虚函数。
9、函数指针
函数指针是指向函数的指针变量。函数指针本身首先是一个指针变量,该指针变量指向一个具体的函数。C在编译时,每一个函数都有一个入口地址,该入口地址就是函数指针所指向的地址。有了指向函数的指针变量后,可用该指针变量调用函数,就如同用指针变量可引用其他类型变量一样。
char * fun(char * p) {…} // 函数fun
char * (*pf)(char * p); // 函数指针pf
pf = fun; // 函数指针pf指向函数fun
pf(p); // 通过函数指针pf调用函数fun
10、fork函数作用
fork:创建一个和当前进程映像一样的进程可以通过fork( )系统调用。成功调用fork( )会创建一个新的进程,它几乎与调用fork( )的进程一模一样,这两个进程都会继续运行。在子进程中,成功的fork( )调用会返回0。在父进程中fork( )返回子进程的pid。如果出现错误,fork( )返回一个负值。
子进程是父进程的副本,它将获得父进程数据空间、堆、栈等资源的副本。
fork函数的特点概括起来就是“调用一次,返回两次”,在父进程中调用一次,在父进程和子进程中各返回一次。
11、map和set有什么区别
12、 STL的allocator
STL的分配器用于封装STL容器在内存管理上的底层细节。在C++中,其内存配置和释放如下:
new运算分两个阶段:(1)调用全局的::operator new配置内存;(2)调用对象构造函数构造对象内容
delete运算分两个阶段:(1)调用对象析构函数;(2)调用全局的::operator delete释放内存
为了精密分工,STL allocator将两个阶段操作区分开来:内存配置有alloc::allocate()负责,内存释放由alloc::deallocate()负责;对象构造由::construct()负责,对象析构由::destroy()负责。
同时为了提升内存管理的效率,减少申请小内存造成的内存碎片问题,SGI STL采用了两级配置器,当分配的空间大小超过128B时,会使用第一级空间配置器;当分配的空间大小小于128B时,将使用第二级空间配置器。第一级空间配置器直接使用malloc()、realloc()、free()函数进行内存空间的分配和释放,而第二级空间配置器采用了内存池技术,通过空闲链表来管理内存。
13、C++源文件从文本到可执行文件经历的过程
对于C++源文件,从文本到可执行文件一般需要四个过程:
预处理阶段:对源代码文件中文件包含关系(头文件)、预编译语句(宏定义)进行分析和替换,生成预编译文件。
编译阶段:将经过预处理后的预编译文件转换成特定汇编代码,生成汇编文件
汇编阶段:将编译阶段生成的汇编文件转化成机器码,生成可重定位目标文件
链接阶段:将多个目标文件及所需要的库连接成最终的可执行目标文件
14、Malloc brk系统调用和mmap系统调用
15、 C++的虚拟内存分段
16、Linux中内存分配
17、如何判断内存泄漏
我们一方面可以使用linux环境下的内存泄漏检查工具Valgrind,另一方面我们在写代码时可以添加内存申请和释放的统计功能,统计当前申请和释放的内存是否一致,以此来判断内存是否泄露。
18、什么时候会发生段错误
段错误通常发生在访问非法内存地址的时候,具体来说分为以下几种情况:
使用野指针
试图修改字符串常量的内容
19、内存泄漏
内存泄漏(memory leak)是指由于疏忽或错误造成了程序未能释放掉不再使用的内存的情况。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,失去了对该段内存的控制,因而造成了内存的浪费
1、 堆内存泄露
2、 系统资源泄露
3、 没有将基类的析构函数定义为虚函数
20、内存共享相关API(应用程序编程接口)
上一篇: C/C++常见面试题整理