复盘

223 阅读16分钟

1.new malloc区别 底层实现

  • new是C++运算符 malloc C/C++标准库函数
  • new自动计算要分配的空间大小,malloc需要手动计算
  • new是类型安全 malloc不是
  • new 会分配空间并调用相关对象的构造函数
  • delete 对指针的对象运行适当的析构函数
  • new是封装malloc直接free不会报错,但是不会析构对象

底层实现: new的实现过程是:首先调用名为operator new的标准库函数,分配足够大的原始为类型化的内存, 以保存指定类型的一个对象;接下来运行该类型的一个构造函数,用指定初始化构造对象;最后返回 指向新分配并构造后的的对象的指针

free: 被free回收的内存会首先被ptmalloc使用双链表保存起来,当用户下次申请内存的时候,先从这里查找合适的返回,避免了频繁的系统调用,占用过多的系统资源,同时ptmalloc 也会尝试对小块内存进行合并,避免过多的内存碎片。

2.main函数有几个参数

  • 3个int argc, char* argv[] char* envp[]
  • 也可以为空 void*
  • 第一个参数argc表示的是传入参数的个数
  • 第二个参数char* argv[],是字符串数组,用来存放指向的字符串参数的指针数组,每一个元素指向一个参数。各成员含义如下:
  • argv[0]:指向程序运行的全路径名
  • argv[1]:指向执行程序名后的第一个字符串 ,表示真正传入的第一个参数
  • argv[2]:指向执行程序名后的第二个字符串 ,表示传入的第二个参数
  • char* envp[]:第三个参数也是一个字符串数组,主要是保存这用户环境中的变量字符串,以NULL结束。envp[]的每一个元素都包含ENVVAR=value形式的字符串,其中ENVVAR为环境变量,value为其对应的值。envp一旦传入,它就只是单纯的字符串数组而已,不会随着程序动态设置发生改变。

3.main函数是程序执行入口吗?

C程序的入口是_start函数,gcc链接器会默认依赖一些库,然后_start会调用main函数 也可以不链接C运行时库,添加参数-nolibc,这样不会自动链接运行库,就不是_start函数作为程序入口了,需要-e指定

4.全局变量存在哪里? 全局初始化变量和未初始化的变量是放在一起吗?

全局区/静态区,初始化好的放静态全局区,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域,程序结束后有系统一起释放

5.可执行文件里面有很多字节,节分很多种,段和节有什么区别?

image.png

  • 通过这张图你可以看到,用户空间内存,从低到高分别是 7 种不同的内存段:
  • 程序文件段,包括二进制可执行代码;
  • 已初始化数据段,包括静态常量 全局变量;
  • 未初始化数据段,包括未初始化的静态变量和全局变量;
  • 堆段,包括动态分配的内存,从低地址开始向上增长;
  • 文件映射段,包括动态库、共享内存等,从低地址开始向上增长(跟硬件和内核版本有关)
  • 栈段,包括局部变量和函数调用的上下文等。栈的大小是固定的,一般是 8 MB。当然系统也提供了 参数,以便我们自定义大小;

6.sizeof 和 strlen 区别

  • sizeof是运算符,并不是函数,结果在编译时得到而非运行中获得;
  • strlen是字符处理的库函数。 sizeof参数可以是任何数据的类型或者数据(sizeof参数不退化);
  • strlen的参数只能是字符指针且结尾 是'\0'的字符串。
  • 因为sizeof值在编译时确定,所以不能用来得到动态分配(运行时分配)存储空间的大小

7.sizeof(指针) 在什么系统下是4,什么决定的?

选择32编译器则是4,64位编译器则是8

8.C++怎么去实现多态的?

  • 重载实现编译时多态,虚函数实现运行时多态
  • 重载:是指允许存在多个同名函数,而这些函数的参数表不同或许参数个数不同,或许参数类型不 同,或许两者都不同)。
  • 覆盖:是指子类重新定义父类的虚函数的做法。

操作上: 在基类的函数前加上virtual关键字,在派生类中重写该函数,运行时将会根据所指对象的实际类型来调 用相应的函数,如果对象类型是派生类,就调用派生类的函数,如果对象类型是基类,就调用基类的函数

原理:

  • 虚表:虚函数表的缩写,类中含有virtual关键字修饰的方法时,编译器会自动生成虚表
  • 虚表指针:在含有虚函数的类实例化对象时,对象地址的前四个字节存储的指向虚表的指针
  • 1.发现有虚函数,构造虚表,一个数组,每个元素代表虚函数的入口地址
  • 2.子类构造时,此时看到了父类对象初始化虚表的指针,令子类vptr指向父类的虚表
  • 然后调用子类的构造函数,当子类没有对父类虚函数重写时,那么派生类虚表指针指向的是父类的虚表,反之指向子类的虚表
  • 2.vptr指针指向对象中所属类的虚表,对应的函数,然而同一个虚表不同函数的指向,通过函数的定义顺序地址进行偏移指向

9.结构体和类的区别

  • 相同点:两者都拥有成员函数 公有和私有部分
  • 任何可以使用class完成的工作,struct同样可以完成
  • 不同点:两者不指定属性,struct默认是公有,class默认是私有
  • class 默认继承是private,struct模式public继承
  • 引申:C++和C的struct区别
  • C中:struct是用户自定义数据类型(UDT);
  • C++中:struct是抽象数据类型(ADT),支持成员函数的定义,C++中的struct能继承,能实现多态

10.vector底层实现,什么地方适合?

  • vector和数组类似,拥有一段连续的内存空间,并且起始地址不变。
  • 因此能高效的进行随机存取,时间 复杂度为o(1);
  • 内存空间是连续的,所以在进行插入和删除操作时,会造成内存块的拷贝,时间复杂度为o(n)。
  • 当数组中内存空间不够时,会重新申请一块内存空间并进行内存拷贝。
  • 它与数组最大的区别就是vector不需程序员自己去考虑容量问题,库里面本身已经实现了容量的动态增长,而数组需要程序员手动写入扩容函数进行扩容。
  • 连续存储结构:vector是可以实现动态增长的对象数组,支持对数组高效率的访问和在数组尾端的删除和插入操作,在中间和头部删除和插入相对不易,需要挪动大量的数据。
  • vector的随机访问效率高,但在插入和删除时(不包括尾部)需要挪动数据,不易操作

11.指针和引用区别?

  • 指针是一个变量,存储的是一个地址,引用跟原来的变量实质上是同一个东西,是原变量的别名
  • 指针可以有多级,引用只有一级
  • 指针可以为空,引用不能为NULL且在定义时必须初始化,初始化之后不可再改变
  • 指针在初始化后可以改变指向
  • sizeof指针得到的是本指针的大小,sizeof引用得到的是引用所指向变量的大小
  • 指针作为参数进行传递时,也是将实参的一个拷贝传递给形参,两者指向的地址相同,但不是同
  • 一个变量,在函数中改变这个变量的指向不影响实参,而引用却可以。
  • 引用本质是一个指针,同样会占4字节内存;
  • 指针是具体变量,需要占用存储空间(具体情况还要 具体分析)。

12.做项目怎么检测内存泄漏?

  • 内存泄漏是指由于疏忽或错误造成了程序未能释放掉不再使用的内存的情况。

  • 计数法:使用new或者malloc时,让该数+1,delete或free时,该数-1,

  • 程序执行完打印这个计数,如果不为0表示存在内存泄露

  • 一定要将基类的析构函数声明为虚函数

  • 对象数组的释放一定要用delete [] 有new就有delete,

  • 有malloc就有free,保证它们一定成对出现

  • Linux下可以使用Valgrind工具

  • 最常用的工具,用来检测程序中出现的内存问题,所有对内存的读写都会被检测到,一切对malloc、free、new、delete的调用都会被捕获。

  • 1、对未初始化内存的使用;

  • 2、读/写释放后的内存块;

  • 3、读/写超出malloc分配的内存块;

  • 4、读/写不适当的栈中内存块;

  • 5、内存泄漏,指向一块内存的指针永远丢失;

  • 6、不正确的malloc/free或new/delete匹配;

  • 7、memcpy()相关函数中的dst和src指针重叠。 使用步骤:

valgrind --leak-check=yes ./myprog arg1 arg2

Windows下可以使用CRT库 试运行DEBUG版程序,运用以下技术:CRT(C run-time libraries)、运行时函数调用堆栈、内存泄漏时,提示的内存分配序号(集成开发环境OUTPUT窗口),综合分析内存泄漏的原因,排除内存泄漏

13.智能指针怎么实现的?shared_ptr unqiue_ptr实现

智能指针是一个类,用来存储指向动态分配对象的指针,负责自动释放动态分配的对象,防止堆内存泄漏。 动态分配的资源,交给一个类对象去管理,当类对象声明周期结束时,自动调用析构函数释放资源

常用的智能指针 (1) shared_ptr 实现原理:采用引用计数器的方法,允许多个智能指针指向同一个对象,每当多一个指针指向该对象时,指向该对象的所有智能指针内部的引用计数加1,每当减少一个智能指针指向对象时,引用计数会减1,当计数为0的时候会自动的释放动态分配的资源。

  • 智能指针将一个计数器与类指向的对象相关联,引用计数器跟踪共有多少个类对象共享同一指针
  • 每次创建类的新对象时,初始化指针并将引用计数置为1
  • 当对象作为另一对象的副本而创建时,拷贝构造函数拷贝指针并增加与之相应的引用计数
  • 对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数(如果引用计数为减至0,则 删除对象),并增加右操作数所指对象的引用计数
  • 调用析构函数时,构造函数减少引用计数(如果引用计数减至0,则删除基础对象) (2) unique_ptr
  • 一个unique_ptr总是拥有它所指向的资源。
  • 转移一个 unique_ptr将会把所有权全部从源指针转移给目标指针,源指针被置空;
  • 所以unique_ptr不支持普通的拷贝和赋值操作,不能用在STL标准容器中;
  • 局部变量的返回值除外(因为编译器知道要返回的对象将要被销毁);
  • 如果你拷贝一个unique_ptr,那么拷贝结束后,这两个unique_ptr都会指向相同的资源,造成在结束时对同一内存指针多次释放而导致程序崩溃。

14.进程和线程区别?

image.png

  • 1、进程是资源调度的基本单位,运行一个可执行程序会创建一个或多个进程,进程就是运行起来的可执行程序
  • 2、线程是程序执行的基本单位,是轻量级的进程。每个进程中都有唯一的主线程,且只能有一个,主线程和进程是相互依存的关系,主线程结束进程也会结束。
  • 协程是用户态的轻量级线程,线程内部调度的基本单位

15.在操作系统上怎么去实现进程和线程的?

16.不同操作系统实现的方式可能不一样?说一说相关

17.每个进程有独立空间,他是怎么去实现这个空间?

18.每个进程怎么保证自己的独立空间的?

分配资源,进程拥有CPU 内存 文件 句柄 进程栈空间 实现对应的调度算法 线程 分配资源,线程栈的大小和地址 实现线程调度算法

参照: (26条消息) 操作系统:进程和线程_xiaocstudy的博客-CSDN博客_操作系统进程与线程

19.系统怎么进入内核态?

这是用户态进程主动要求切换到内核态的一种方式,用户态进程通过系统调用申请使用操作系统提供的服务程序完成工作,比如fork()实际上就是执行了一个创建新进程的系统调用。 而系统调用的机制其核心还是使用了操作系统为用户特别开放的一个中断。

(26条消息) 操作系统~用户态进入内核态的方式(中断、异常、系统调用)_Listen-Y的博客-CSDN博客_进入内核态的操作

20.通过什么去产生系统中断?

系统调用

  • 这是用户态进程主动要求切换到内核态的一种方式,用户态进程通过系统调用申请使用操作系统提供的服务程序完成工作,比如fork()实际上就是执行了一个创建新进程的系统调用。而系统调用的机制其核心还是使用了操作系统为用户特别开放的一个中断来实现。

异常

  • 当CPU在执行运行在用户态下的程序时,发生了某些事先不可知的异常,这时会触发由当前运行进程切换到处理此异常的内核相关程序中,也就转到了内核态,比如缺页异常。

外围设备的中断

  • 当外围设备完成用户请求的操作后,会向CPU发出相应的中断信号,这时CPU会暂停执行下一条即将要执行的指令转而去执行与中断信号对应的处理程序,如果先前执行的指令是用户态下的程序,那么这个转换的过程自然也就发生了由用户态到内核态的切换。
  • 比如硬盘读写操作完成,系统会切换到硬盘读写的中断处理程序中执行后续操作等。
  • 这3种方式是系统在运行时由用户态转到内核态的最主要方式,其中系统调用可以认为是用户进程主动发起的,异常和外围设备中断则是被动的。

21.堆和栈的区别?

22.堆的分配内存原理?

23.栈空间怎么分配的?

  • 申请方式不同:
  • 栈由系统自动分配
  • 堆由自己申请和释放的
  • 申请大小限制不同:
  • 栈顶和栈底是之前预设好的,栈是向栈底扩展,大小固定,可以通过ulimit -a查看,ulimit -s修改
  • 堆向高地址扩展,是不连续的内存区域,大小可以灵活调整。
  • 申请效率不同:
  • 栈由系统分配,速度快,不会有碎片
  • 堆由程序员分配,速度慢,且会有碎片

image.png

image.png

24.一个函数的整个调用流程?函数是怎么调用的?参数怎么压栈?

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

25.TCP和UDP主要区别?

(26条消息) TCP和UDP的区别_ZJE_ANDY的博客-CSDN博客_tcp udp

26.ARP协议有了解吗?

  • 地址解析协议,即ARP(Address Resolution Protocol),是根据IP地址获取物理地址的一个TCP/IP协议
  • 主机发送信息时将包含目标IP地址的ARP请求广播到局域网络上的所有主机,并接收返回消息,以此确定目标的物理地址;
  • 收到返回消息后将该IP地址和物理地址存入本机ARP缓存中并保留一定时间,下次请求时直接查询ARP缓存以节约资源。
  • 地址解析协议是建立在网络中各个主机互相信任的基础上的,局域网络上的主机可以自主发送ARP应答消息,其他主机收到应答报文时不会检测该报文的真实性就会将其记入本机ARP缓存;
  • 由此攻击者就可以向某一主机发送伪ARP应答报文,使其发送的信息无法到达预期的主机或到达错误的主机,这就构成了一个ARP欺骗
  • ARP命令可用于查询本机ARP缓存中IP地址和MAC地址的对应关系、添加或删除静态对应关系等。

27.编译原理学过吗?

编译原理知识汇总 - 简书 (jianshu.com)

28.大端和小端怎判断? 有什么区别?怎么用代码判断?

(26条消息) 大端和小端的含义及判断代码_YoungYangD的博客-CSDN博客