qi-an-xin

文章目录

  • 奇安信一面:
  • 奇安信二面

奇安信一面:


先介绍项目
迭代器++it和++it哪个好?
前置迭代返回一个引用 后置的返回一个对象。临时对象会导致效率降低。

网络字节序
TCPdump抓包
IP地址转网络字节序 pton:将用字符串表示的IP地址转换成用网络字节序表示的整数
IP头部中校验和校验的内容(就是IP本身)
Main函数执行之前的过程(一些系统调用库函数类似的)

全双工通信 和半双工通信涉及到的协议 通信速率问题

网络路由协议
IP协议的一个核心任务就是数据报的路由。即决定发送数据报到目标机器的路径。
IP数据报应该发送至哪个下一跳路由,以及经过哪个网卡来发送,就是路由过程。IP模块实现数据报路由的核心数据结构就是路由表。
IP的路由机制:
1) 查找路由表中和数据报的目标IP地址完全匹配的主机IP地址,如果找到,就是用该项。否则进入下一步;
2) 查找路由表中和数据报的目标IP地址具有相同网络ID的网络IP地址,如果找到,就使用该项。否则进入下一步;
3) 选择默认路由项,这通常意味着数据报的下一跳路由就是网关;

DNS域名解析
ISO七层模型和每一层的协议
空类占位问题
结构体的内存对齐问题
结构体的大小不能,进行简单的元素的大小相加,结构体的内存布局,要考虑内存对齐。
内存对齐应符合以下几个规则:
1、 分配内存的顺序是按照声明的顺序。
2、 **每个变量相对于起始位置的偏移量必须是该变量类型大小(自身对齐数)的整数倍,不是整数倍空出内存,直到偏移量是整数倍为止。**其他成员变量除了第一个之外,要对齐(起始偏移量要能整除该成员的对齐数)到某个数字(对齐数 ---- 不考虑编译器设置对齐数的情况下指的是自身大小)的整数倍的地址处。
3、 最后整个结构体的大小必须是最大对齐数的整数倍

Weak_ptr指针是为了解决什么问题而出现的
循环引用问题:循环引用是指使用多个智能指针share_ptr时,出现了指针之间相互指向,从而形成环的情况,有点类似于死锁的情况,这种情况下,智能指针往往不能正常调用对象的析构函数,从而造成内存泄漏。
假设存在A和B相互引用,使用shared_ptr时计数器计数值会增加为2:即使是被销毁后,引用计数也只是变为1,不会销毁,这样就出现了内存泄露。为了解决这种循环引用问题,引入了特殊的智能指针:weak_ptr 这种智能指针在指向对象时,对象的引用计数不会加一。指针和对象一起销毁。

**独占型智能指针:**某个时刻只能有一个unique_ptr指向一个动态分配的资源对象,也就是这个资源不会被多个unique_ptr对象同时占有,它所管理的资源只能在unique_ptr对象之间进行移动,不能拷贝,所以它只提供了移动语义
一旦unique_ptr对象被销毁,它所管理的资源一共销毁;

多线程实现同步的无锁方式
qi-an-xin_第1张图片
进程和线程共享的内容和不共享的内容
进程上下文
生产者–消费者模型设计(两个资源型信号量 empty full 一个锁变量mutex
堆栈中存放的内容
Const关键字
New_placement关键字 预留内存空间问题
宏定义const int a 在C++和C中的区别(在作用域上小小的差别)

程序编译过程 宏和函数的相关问题

动态链接和静态链接问题
浏览器连不上网络 你会怎么做(逐步排查 路由器网线 DNS解析出现问题等)
重新进入控制面板进行DNS服务器地址的配置;


补充:
1、 传递函数时,什么时候用指针 什么时候用引用
需要返回函数内局部变量的内存的时候用指针。
对栈空间大小比较敏感(比如递归)的时候用引用。

2、C++异常处理机制:主要使用try,throw,和catch三个关键字;
程序的执行流程是先执行 try包裹的语句块,如果执行过程中没有异常发生,则不会进入任何catch包裹的语句块,如果发生异常,则使用throw进行异常抛出,再由catch进行捕获,throw可以抛出各种数据类型的信息,代码中使用的是 数字,也可以自定义异常class。

3、形参和实参的区别

  1. 形参变量只有在被调用时才分配内存单元,在调用结束时, 即刻释放所分配的内存单元。因此,形参只有在函数内部有效。 函数调用结束返回主调函数后则不能再使用该形参变量。 2) 实参可以是常量、变量、表达式、函数等, 无论实参是何种类型的量,在进行函数调用时,它们都必须具有确定的值, 以便把这些值传送给形参。 因此应预先用赋值,输入等办法使实参获得确定值, 会产生一个临时变量。
  2. 实参和形参在数量上,类型上,顺序上应严格一致, 否则会发生“类型不匹配”的错误。 4) 函数调用中发生的数据传送是单向的。 即只能把实参的值传送给形参,而不能把形参的值反向地传 送给实参。 因此在函数调用过程中,形参的值发生改变,而实参中的值不会变化。
  3. 当形参和实参不是指针类型时,在该函数运行时,形参和实参是不同的变量,他们在内存中位于不同的位置,形参将实参的内容复制一份,在该函数运行结束的时候形参被释放,而实参内容不会改变。

4、new和delete的实现原理
1、new简单类型直接调用operator new分配内存;
而对于复杂结构,先调用operator new分配内存,然后在分配的内存上调用构造函数;
对于简单类型,new[]计算好大小后调用operator new; 对于复杂数据结构,new[]先调用operator new[]分配内存,然后在p的前四个字节写入数组大小n,然后调 用n次构造函数,针对复杂类型,new[]会额外存储数组大小; ① new表达式调用一个名为operator new(operator new[])函数,分配一块足够大的、原始的、未命名的内 存空间; ② 编译器运行相应的构造函数以构造这些对象,并为其传入初始值; ③ 对象被分配了空间并构造完成,返回一个指向该对象的指针。

2、delete简单数据类型默认只是调用free函数 复杂数据类型先调用析构函数再调用operator delete;
针对简单类型,delete和delete[]等同。假设指针p指向new[]分配的内存。因为要4字节存储数组大小,实际 分配的内存地址为[p-4],系统记录的也是这个地址。delete[]实际释放的就是p-4指向的内存。而delete会 直接释放p指向的内存,这个内存根本没有被系统记录,所以会崩溃。
3、 需要在 new [] 一个对象数组时,需要保存数组的维度,C++ 的做法是在分配数组空间时多分配了 4 个字节的大小,专门保存数组的大小,在 delete [] 时就可以取出这个保存的数,就知道了需要调用析构 函数多少次了。

5、赋值初始化和列表初始化的区别:

  1. 赋值初始化,通过在函数体内进行赋值初始化;
  2. 列表初始化,在冒号后使用初始化列表进行初始化。
  3. 这两种方式的主要区别在于:
    对于在函数体中初始化,是在所有的数据成员被分配内存空间后才进行的。
    列表初始化是给数据成员分配内存空间时就进行初始化(进入函数体之前),就是说分配一个数据成员只要冒号后有此数据成 员的赋值表达式(此表达式必须是括号赋值表达式),那么分配了内存空间后在进入函数体之前给数据成员赋值,就是说初始化这个数据成员此时函数体还未执行。
    初始化列表的应用场景:
  4. 必须使用成员初始化的四种情况 ① 当初始化一个引用成员时; ② 当初始化一个常量成员时; ③ 当调用一个基类的构造函数,而它拥有一组参数时; ④ 当调用一个成员类的构造函数,而它拥有一组参数时;
  5. 成员初始化列表做了什么 ① 编译器会一一操作初始化列表,以适当的顺序在构造函数之内安插初始化操作,并且在任何显示用户代码之前; ② list中的项目顺序是由类中的成员声明顺序决定的,不是由初始化列表的顺序决定的;
    3)为什么用初始化列表会快一些:因为它是在分配内存空间的同时进行初始化,是在进入函数体之前操作的,因此相比在函数体内进行初始化操作的赋值初始化,会少一次调用拷贝构造函数的过程,开销小。

6、C++中的string和C中的char*有何不同?
string继承自basic_string,其实是对char进行了封装,封装的string包含了char数组,容量,长度等等属性。 string可以进行动态扩展,在每次扩展的时候另外申请一块原空间大小两倍的空间(2^n),然后将原字符串拷贝过去,并加上新增的内容。
7、内存泄露以及检测和避免
内存泄露 一般我们常说的内存泄漏是指堆内存的泄漏。堆内存是指程序从堆中分配的,大小任意的(内存块的大 小可以在程序运行期决定)内存块,使用完后必须显式释放的内存。应用程序般使用malloc,、realloc、 new等函数从堆中分配到块内存,使用完后,程序必须负责相应的调用free或delete释放该内存块,否则,这块内存就不能被再次使用,我们就说这块内存泄漏了
避免内存泄露的几种方式
计数法:使用new或者malloc时,让该数+1,delete或free时,该数-1,程序执行完打印这个计数,如果不为0则表示存在内存泄露
一定要将基类的析构函数声明为虚函数,防止只析构基类不析构派生类的情况
对象数组的释放一定要用delete []
有new就有delete,有malloc就有free,保证它们一定成对出现
检测工具 Linux下可以使用Valgrind工具 Windows下可以使用CRT库

8、函数调用的压栈过程
通过这样一段代码来具体讲解:
qi-an-xin_第2张图片
1) 当函数从入口函数main函数开始执行时,编译器会将我们操作系统的运行状态,main函数的返回地址、 main的参数、mian函数中的变量、进行依次压栈;
2) 当main函数开始调用func()函数时,编译器此时会将main函数的运行状态进行压栈,再将func()函数的返回地址、func()函数的参数从右到左、func()定义变量依次压栈;
3) 当func()调用f()的时候,编译器此时会将func()函数的运行状态进行压栈,再将f()的返回地址、f()函数的参数从右到左、f()定义变量依次压栈 从代码的输出结果可以看出,函数f(var1)、f(var2)依次入栈,而后先执行f(var2),再执行f(var1),最后打印整个字符串,将栈中的变量依次弹出,最后主函数返回。

文字化表述:
函数的调用过程
1)从栈空间分配存储空间
2)从实参的存储空间复制值到形参栈空间
3)进行运算
形参在函数未调用之前都是没有分配存储空间的,在函数调用结束之后,形参弹出栈空间,清除形参空间。 数组作为参数的函数调用方式是地址传递,形参和实参都指向相同的内存空间,调用完成后,形参指针被销毁,但是所指向的内存空间依然存在,不能也不会被销毁。 当函数有多个返回值的时候,不能用普通的 return 的方式实现,需要通过传回地址的形式进行,即地址/ 指针传递

9、strcpy和memcpy的区别是什么?
1、复制的内容不同 strcpy只能复制字符串,而memcpy可以复制任意内容,例如字符数组、整型、结 构体、类等。
2、复制的方法不同 strcpy不需要指定长度,它遇到被复制字符的串结束符"\0"才结束,所以容易溢出。memcpy则是根据其第3个参数决定复制的长度。
3、用途不同 通常在复制字符串时用strcpy,而需要复制其他类型数据时则一般用memcpy.


奇安信二面

三个项目进行介绍
穿插问了一些问题:
Socket通信过程的遇到的问题以及怎么排查解决的:数据报的丢失
Gdb的调试:一段程序出现了问题 怎么用gdb进行向上的追踪;
TCP客户端服务端程序奔溃的问题;
Tcpdump抓包
实现一个web服务器应该具备哪些模块;
Tcp/IP中三次握手 四次挥手以及状态的变迁 详细讲述;
发送FIN终止控制之后 为什么不直接close而是四次挥手;
下载的时候为什么先缓慢上升再趋于平稳(拥塞控制)
存钱账户的金额变少了 怎么排查(日志系统记录错误
C++的STL中常见的容器:vector list map set multimap multiset unordered_map 容器的特点和实现方式 底层实现原理
对于不同的容器 迭代器失效有什么不同
C++面向对象三大特性:封装 继承 多态 详细介绍三个特性(多态实现的两个必要条件)

你可能感兴趣的:(C++基础,数据结构与算法,实习经验,网络,c++,数据结构)