2017、07、05
一、C++中常见的面试题:
1、指针和数组的区别:
定义一个数组int arr[0 ] ={0}; arr[1] =20;
定义一个指针int a = 20; int *p = &a;
在汇编上:
int arr[10] = {0};
000813BE mov dword ptr [arr],0
000813C5 xor eax,eax
000813C7 mov dword ptr [ebp-28h],eax
000813CA mov dword ptr [ebp-24h],eax
000813CD mov dword ptr [ebp-20h],eax
000813D0 mov dword ptr [ebp-1Ch],eax
000813D3 mov dword ptr [ebp-18h],eax
000813D6 mov dword ptr [ebp-14h],eax
000813D9 mov dword ptr [ebp-10h],eax
000813DC mov dword ptr [ebp-0Ch],eax
000813DF mov dword ptr [ebp-8],eax
arr[2] = 20;
000813E2 mov dword ptr [ebp-24h],14h
指针:
int a = 20;
012913BE mov dword ptr [a],14h
int *p =&a;
012913C5 lea eax,[a]
012913C8 mov dword ptr [p],eax
2、指针与引用的区别:
在C语言里没有引用,在C++11以前只有一级引用,以后有左引用和右引用,int &s =a; 013013C5 lea eax,[a]
013013C8 mov dword ptr [s],eax
在汇编上指针跟引用没有区别。但在使用上:使用引用变量会开辟内存但是没用过;引用变量在汇编上是指针;访问引用变量访问的是它所引用的内存,所以a = *p =s,指的是同一块内存;引用必须初始化,初始化的值必须取地址;引用能变;
2.函数的重载
定义:函数名相同,参数个数不同,不能仅根据返回值不同来区分函数是否重载;且要在同一个作用域,
void fun(int a);
void fun(const int a );不能构成重载;
用指针和引用修饰才能构成重载。改成void fun(int *a);void fun(const int *a);因为C++在编译阶段会产生符号表,链接时要进行符号解析,符号解析完成后要给符号分配地址,因为C++编译函数产生的符号表是函数名+参数,所以同一个符号只能出现一次。在重载的过程中,首先会进行参数个数相同,同类型的匹配,如果没有会退化为内置类型的隐式转换,在没有就是找不到。
3.多态分为静多态和动多态
静多态是指编译时期的多态:函数的重载在编译时期已经确定要调用哪个函数;模板,在编译时期已经确认要实例化哪个模板;
动多态是指运行时的多态:主要指继承里面的虚函数,虚函数在编译时期会产生一个虚函数表,将函数的地址写在虚函数表里,当基类指针指向不同派生类对象,通过指针调用基类和派生类的同名覆盖方法,会发生多态,在链接的时候会将虚函数表加载到只读数据段(rdata)内;虚函数表与类型一一对应,运行时从vfptr找到vftable,从表里面取地址,指向谁就调用谁。
4.在函数调用的时候会出现绑定问题:
分为早绑定和晚绑定
早绑定就是静态绑定;也就是在编译时期 已经确认好要调用哪个函数:call base::show addr
晚绑定就是动态的绑定,在运行时期:mov eax,dword ptr[obj]
mov ecx,dword ptr[eax]
call ecx
因为call 寄存器 ,就看运行时寄存器里面的地址指向谁就说明调用哪个函数。
5、列举一些内存泄漏的原因:
内存只申请不释放,malloc完了之后忘记free,new之后忘了delete;
浅拷贝,或者在赋值运算符重载的时候,operator=,没有释放现有的内存,直接赋值;
智能指针的交叉使用;
基类指针指向在堆内生成的派生类对象,析构时;
New[]开辟了一段内存,用delete释放;
在open没有close,FILE* pf = open();
构造函数抛出异常,在抛出异常之前申请的内存没得到释放就泄露了;
Linux上,fd泄漏了,因为fd上限65535;
Linux上如果出现僵尸进程,会造成内核资源的泄漏;
例如:int *fun(){ int * p= new int ; return p;}
在调用函数完成后要对资源进行释放;、
6、深究一下malloc 底层的实现:
Malloc ----- ptmalloc ---- (do_fork()/do_mmap);
Ptmalloc ------ bins[128] -------- chunk;
画出虚拟地址空间、在gcc上的实现、数据结构、内核上;
7.STL里有哪些容器
顺序容器:Vector: List : deque;
关联容器:set/multiset;map/multimap;hashmap;
容器适配器:stack (deque); queue(deque) ;priority_queue(vector大根堆);没有迭代器;
Reserve()/resize();
8.设计模式:单例模式
分析多线程是否安全的思路:
首先看进程的结果会不会随着线程调度顺序的不同而不同,如果会,看是不是存在竞态条件,把存在竞态条件的代码段称为临界区代码段,再看临界区代码段进行的是不是原子操作,如果不是就要加互斥锁。再就是你加了互斥锁以后,既要保证多线程的安全又要保证单线程的效率,所以进行二次判断。
工厂/抽象工厂模式
观察者模式
9、编译链接的原理:
预编译:处理所有以#开头的预编译指令、以及注释
编译:进行语法检查,代码优化,符号的汇总
汇编:根据汇编码以及特定的操作平台生成机器码
生成二进制可重定位的目标文件*.obj结尾:包含了text、data、bss、以及符号表、section table(保留了所有段的信息);
链接:第一步 合并所有段表,合并符号表,进行符号解析、为符号分配虚拟内存地址。
第二步:符号的重定向
运行的时候从main函数开始:创建虚拟地址空间到物理内存的映射,第一次映射的时候会发生缺页异常,需要分配物理页面,然后将其放入相应的页表。然后找到程序头program header,里面有两load,母的是为了表明把哪些段加载带代码段或者把哪些段加载到数据段,加载完成之后把可执行文件的入口地址写到CPU的pc寄存器里面,从磁盘映射到虚拟内存的映射方式是mmap,代码段数据段一一对应,虚拟内存上多一个文件头,然后再讲虚拟内存映射到页面上,映射方式是页目录页表映射。
二、系统调用的过程:
系统调用的过程依赖于系统调用号和系统调用表;
在调用系统函数read、open的时候产生软中断,0x80,16进制,也就是128,陷入内核,从用户空间却换到内核空间,换下去之前将系统调用号放入eax中,操作系统开始执行,从eax中取出系统调用号,再去系统调用表中找对应的系统调用。切换的开销大,避免反复切换。