热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

千万不要错过的后端[纯干货]面试知识点整理II

千万不要错过的后端【纯干货】面试知识点整理IIc++内存管理上次分享整理的面试知识点I,今天我们来继续分享面试知识点整理IIlinuxkernel内核空间、内存管理、进程管理设备、






千万不要错过的后端【纯干货】面试知识点整理 I I

c++内存管理

上次分享整理的面试知识点 I , 今天我们来继续分享面试知识点整理 II













































linux kernel 内核空间、内存管理、进程管理设备、驱动虚拟文件系统(vfs)内核空间是受保护的,用户不能对内核空间读写,否则会出现段错误
环境变量(env)PATH
命令行参数char *agrv[]
栈区⬇️函数的返回地址,返回值,参数,局部变量
共享库(映射区)⬇️调用动态库,或者mmap函数进行文件映射
堆区⬆️用new/malloc申请的内存,同时需要适用delete/free来释放采用链式储存结构
.bss区未初始化的全局变量和静态变量以及 初始化为 0 的 全局变量和静态变量编译时就已经分配了空间
.data区已初始化的全局变量和静态变量编译时就已经分配了空间
.text1、只读存储区 – 常量,const全局变量2、文本区 – 程序代码,机器代码
0-4k保护区

#include<stdio.h>
int a; //未初始化全局区 .bss
int b=1; //已初始化全局区 .data
static int c=2; //已初始化全局区 .data
const int d=3; //只读数据段,也叫文字常量区 ro.data, d的值不能被修改
int main(void)
{
int e=4; //栈区
static int f=5; //已初始化全局区
const int g=6; //栈区,不能通过变量名修改其值,但可通过其地址修改其值
int *p=malloc(sizeof(int)) //指针变量p在栈区,但其所指向的4字节空间在堆区
char *str="abcd"; //字符串“abcd”存在文字常量区,指针变量str在栈区,存的是“abcd”的起始地址
return 0;
}

内存泄露及分类

img

内存泄漏,是由于疏忽或错误造成程序未能释放掉不再使用的内存。内存泄漏,并不是指内存内存再物理地址上的消失,而是应用程序分配某段内存后,失去了对该段内存的控制,因而造成内存的浪费。



  • 一般情况是new/malloc 后,没有及时delete/free释放内存,判断为内存泄露
  • linux中可以使用valgrind来检测内存泄漏

内存泄漏的分类:



  • 堆内存泄漏 — new/malloc 后 没有delete/free掉
  • 系统资源泄漏 — 系统分配的资源,没有用指定的函数释放掉,导致系统资源的浪费,严重影响系统性能,如:socket,bitmap,handle
  • 没有将父类的析构函数定义为虚函数 — 父类指针指向子类对象的时候,释放内存的时候,若父类的析构函数不是virtual的话,子类的内存是不会得到释放的,因此会内存泄漏

c++中是如何处理内存泄漏的:

使用valgrind,mtrace来检测内存泄漏

避免内存泄漏:

1.事前预防型。如智能指针等。 2.事后查错型。如泄漏检测工具。


智能指针

使用智能指针,智能指针会自动删除被分配的内存,他和普通指针类似,只是不需要手动释放指针,智能指针自己管理内存释放,不用担心内存泄漏问题

智能指针有:



  • auto_ptr
  • unique_ptr
  • shared_ptr
  • weak_ptr

其中auto_ptr c++11已经被弃用了


unique_ptr

独占的智能指针,只能有一个对象拥有所有权,独占指针的是自己管理内存的,指针存在于栈空间,开辟的内存在堆空间,这里的堆空间是和智能指针绑定的,智能指针随着函数结束被销毁之前,智能指针会先去把堆里面的内存销毁

其中涉及



  • move函数 – 可以使用move函数来转移所有权,转移所有权后,原来的指针就无权访问


  • reset函数 – 可以用reset函数来重置所有权,会把之前的对象所有权释放掉,重新创建一个所有权对象


  • make_unique – 快速的创建一个unique_ptr智能指针的对象 如 auto myptr = make_unique();


如果希望只有一个智能指针管理资源 就使用 unique_ptr

#include <iostream>
#include <string>
#include <memory>
using namespace std;
struct person
{
~person()
{
cout<<"~person"<<endl;
}
string str;
};
unique_ptr<person> test()
{
return unique_ptr<person> (new person);
}
int main()
{
//unique_ptr is ownership
unique_ptr<person> p = test();
p->str = "hello world";
unique_ptr<person> p2 = move(p); //可以使用move函数来转移所有权,转移所有权后,原来的指针就无权访问
if(!p)
{
cout<<"p == null" <<endl;
}
if(p2)
{
cout<<"p2 have ownership"<<endl;
cout<<p2->str<<endl;
}
p2.reset(new person);//可以用reset函数来重置所有权,会把之前的对象所有权释放掉,重新创建一个所有权对象
if(p2->str.empty())
{
cout<<"str is null"<<endl;
}
return 0;
}

shared_ptr

共享的智能指针,shared_ptr使用引用计数(use_count方法),每个shared_ptr的拷贝都指向同一块内存,在最后一个shared_ptr被析构的时候,内存才会被释放



  • shared_ptr 是引用计数的方式,使用use_count查看计数


  • make_shared 快捷创建 shared_ptr


使用函数返回自己的shared_ptr时,需要继承enable_shared_from_this类,使用shared_from_this函数进行返回

注意事项:



  • 不要将this指针作为返回值


  • 要避免循环引用


  • 不要再函数实参种创建shared_ptr,在调用函数之前先定义以及初始化它


  • 不要用一个原始指针初始化多个shared_ptr


希望多个指针管理同一个资源就使用shared_ptr

#include <iostream>
#include <string>
#include <memory>
using namespace std;
struct person
:enable_shared_from_this<person>{
string str;
void show()
{
cout<<str<<endl;
}
~person()
{
cout<<"~person"<<endl;
}
shared_ptr<person> getshared()
{
return shared_from_this();
}
};
int main()
{
#if 0
shared_ptr<person> ptr(new person);
cout<< ptr.use_count()<<endl;
shared_ptr<person> ptr2 = ptr;
cout<< ptr.use_count()<<endl;
shared_ptr<person> a = make_shared<person>();
cout<< a.use_count()<<endl;
a = ptr2;
cout<< ptr.use_count()<<endl;
shared_ptr<person> mm = a->getshared();
#endif
shared_ptr<person> ptr;
{
shared_ptr<person> ptr2(new person);
ptr2->str = "hello";
ptr = ptr2->getshared();
cout<< ptr.use_count()<<endl;
}
ptr->show();
return 0;
}

weak_ptr

弱引用的智能指针

是用来监视shared_ptr的,不会使用计数器加1,也不会使用计数器减1,主要是为了监视shared_ptr的生命周期,更像是shared_ptr的一个助手。weak_ptr还可以用来返回this指针和解决循环引用的问题。

shared_ptr会有循环引用的问题 ,解决方式为 把类中的shared_ptr 换成 weak_ptr即可

struct ListNode
{
std::shared_ptr<ListNode> _next;//std::weak_ptr _next; 就可以解决
std::shared_ptr<ListNode> _prev;//std::weak_ptr _pre; 就可以解决
~ListNode()
{
cout << "~ListNode()" << endl;
}
};
void test_shared_ptr_cycleRef()
{
std::shared_ptr<ListNode> cur(new ListNode);
std::shared_ptr<ListNode> next(new ListNode);
cur->_next = next;
next->_prev = cur;
}
int main()
{
test_shared_ptr_cycleRef();
system("pause");
return 0;
}

例如上述代码案例

void shared_ptr_cycleRef(){
std::shared_ptr<LISTNODE> cur LISTNODE;
std::shared_ptr<LISTNODE> next LISTNODE;
cur->_next = next;
next->_pre = cur;
}

Cur 和 next 存在循环引用,他们的引用计数都变为 2

出了作用域之后,cur 和 next 被销毁,引用计数减 1

因此要释放cur , 就需要释放next 的 _pre,要释放next , 就需要释放cur 的 _next


内存泄漏检测工具


valgrind内存检测工具

valgrind的官方网址是:http://valgrind.org

valgrind被设计成非侵入式的,它直接工作于可执行文件上,因此在检查前不需要重新编译、连接和修改你的程序。要检查一个程序很简单

命令如下: valgrind --tool=tool_name program_name



  • 做内存检查: valgrind --tool=memcheck ls -l
  • 检查内存泄漏: valgrind --tool=memcheck --leak-check=yes ls -l

valgrind有如下几个工具

memcheck

memcheck 探测程序中内存管理存在的问题。

它检查所有对内存的读/写操作,并截取所有的malloc/new/free/delete调用。因此memcheck工具能够探测到以下问题:

Memcheck 工具主要检查下面的程序错误:



  • 使用未初始化的内存 (Use of uninitialised memory)


  • 使用已经释放了的内存 (Reading/writing memory after it has been free’d)


  • 使用超过 malloc分配的内存空间(Reading/writing off the end of malloc’d blocks)


  • 对堆栈的非法访问 (Reading/writing inappropriate areas on the stack)


  • 申请的空间已经释放释放,即内存泄漏 (Memory leaks – where pointers to malloc’d blocks are lost forever)


  • malloc/free/new/delete申请和释放内存的匹配(Mismatched use of malloc/new/new [] vs free/delete/delete [])


  • src和dst的重叠(Overlapping src and dst pointers in memcpy() and related functions)


cachegrind

cachegrind 是一个cache剖析器。

它模拟执行CPU中的L1, D1和L2 cache,

因此它能很精确的指出代码中的cache未命中。

它可以打印出cache未命中的次数,内存引用和发生cache未命中的每一行 代码,每一个函数,每一个模块和整个程序的摘要。

若要求更细致的信息,它可以打印出每一行机器码的未命中次数。

在x86和amd64上, cachegrind通过CPUID自动探测机器的cache配置,所以在多数情况下它不再需要更多的配置信息了。

helgrind

helgrind查找多线程程序中的竞争数据。

helgrind查找内存地址,那些被多于一条线程访问的内存地址,但是没有使用一致的锁就会被查出。这表示这些地址在多线程间访问的时候没有进行同步,很可能会引起很难查找的时序问题。


产生段错误的原因



  • 使用野指针
  • 试图对字符串常量进行修改

new和malloc的区别:

在申请内存时



  • new是一个操作符,可以被重载,malloc是一个库函数


  • new在申请内存的时候,会按照对象的数据结构分配内存,malloc分配指定的内存大小


  • new申请内存时,会调用构造函数,malloc不会


  • new申请内存时,返回对象的指针,malloc申请内存的时候,返回(void *) 因此需要强转


  • 申请数组的时候,new[],会一次性分配所有内存,调用多个构造函数,因此需要delete[]来销毁内存,调用多次析构函数,而 malloc 只能sizeof(int)*n


  • new申请内存失败,会抛bac_malloc异常, malloc申请失败则返回NULL


  • malloc当分配的内存不够的时候,会使用realloc再次分配内存, new没有这样的机制。


  • new分配的内存需要用delete释放,delete 会调用析构函数,malloc分配的内存需要free 函数释放



realloc的原理:

realloc是在C语言中出现的,c++已经摒弃realloc函数,realloc函数分配一块新内存的时候,会把原内存中的内存copy到新内存中,通过memmove的方式


共享内存相关的api



  • shmget 新建共享内存
  • shmat 连接共享内存到当前地址空间
  • shmdt 分离共享内存
  • shmctl 控制共享内存

c++ STL内存优化


c++11新特性:

关键字和语法



  • auto关键字

编译器可以根据初始化来推导数据类型,不能用于函数传参和以及数组类型推导



  • nullptr关键字

一种特殊类型的字面量,可以被转成任意的其他类型



  • 初始化列表

初始化类的列表



  • 右值引用

可以实现移动语义和完美转发,消除两个对象交互时不必要的拷贝,节省存储资源,提高效率

新增容器



  • 新增STL array ,tuple、unordered_map,unordered_set

智能指针,内存管理



  • 智能指针

新增 shared_ptr、weak_ptr用于内存管理

多线程



  • atomic原子操作

用于多线程互斥

其他



  • lamda表达式

可以通过捕获列表访问上下文的数据



  • std::function std::bin d封装可执行对象

防止头文件重复引用:

#ifndef

作用:相同的两个文件不会被重复包含。

优点:



  • 受C/C++语言标准的支持,不受编译器的限制。
  • 不仅仅局限于避免同一个文件被重复包含,也能避免内容完全相同的两个文件(或代码片段)被重复包含。

缺点:



  • 如果不同头文件中的宏名恰好相同,可能就会导致你看到头文件明明存在,编译器却说找不到声明的情况。
  • 由于编译器每次都需要打开头文件才能判定是否有重复定义,因此在编译大型项目时,#ifndef会使得编译时间相对较长。

#pragma once

作用:物理上的同一个文件不会被重复包含。

优点:



  • 避免#ifndef中因为宏名相同导致的问题。
  • 由于编译器不需要打开头文件就能判定是否有重复定义,因此在编译大型项目时,比#ifndef更快。

缺点:



  • #pragma once只针对同一文件有效,对相同的两个文件(或代码片段)使用无效
  • #pragma once不受一些较老版本的编译器支持,一些支持了的编译器又打算去掉它,所以它的兼容性可能不够好。

继承与组合



  • 继承是面向对象三大基本特征之一(继承,封装,多态),继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为,继承强调的是is-a关系,是‘白盒式’的代码复用
  • 组合是通过对现有对象进行拼装即组合产生新的具有更复杂的功能,组合体现的是整体和部分,强调的是has-a的关系,是‘黑盒式’的代码复用

继承与组合使用场景



  • 逻辑上B 是A 的“一种”a kind of

继承 (如 男人 继承 人类)



  • 逻辑上A 是B 的“一部分”a part of

组合(如 组合 眼 耳 口 鼻 -> 头)

继承与组合区别



  • 在继承中,父类的内部细节对子类可见,其代码属于白盒式的复用,调的是is-a的关系,关系在编译期就确定
  • 组合中,对象之间的内部细节不可见,其代码属于黑盒式复用。强调的是has-a的关系,关系一般在运行时确定

继承与组合优缺点

继承

优点:



  • 支持扩展,通过继承父类实现,但会使系统结构较复杂
  • 易于修改被复用的代码

缺点:



  • 代码白盒复用,父类的实现细节暴露给子类,破坏了封装性
  • 当父类的实现代码修改时,可能使得子类也不得不修改,增加维护难度。
  • 子类缺乏独立性,依赖于父类,耦合度较高
  • 不支持动态拓展,在编译期就决定了父类

组合

优点:



  • 代码黑盒复用,被包括的对象内部实现细节对外不可见,封装性好。
  • 整体类与局部类之间松耦合,相互独立。
  • 支持扩展
  • 每个类只专注于一项任务
  • 支持动态扩展,可在运行时根据具体对象选择不同类型的组合对象(扩展性比继承好)

缺点:



  • 创建整体类对象时,需要创建所有局部类对象。导致系统对象很多。

函数指针的好处和作用:

好处:简化结构和程序通用性的问题,也是实现面向对象编程的一种途径

作用:



  • 实现面向对象编程中的多态性


  • 回调函数



inline函数与宏定义

inline函数是C++引入的机制,目的是解决使用宏定义的一些缺点。


为什么要引入内联函数(内联函数的作用)

用它替代宏定义,消除宏定义的缺点。


宏定义使用预处理器实现,做一些简单的字符替换因此不能进行参数有效性的检测。



  • inline 相比宏定义有哪些优越处


  • inline 函数代码是被放到符号表中,使用时像宏一样展开,没有调用的开销效率很高;


  • inline 函数是真正的函数,所以要进行一系列的数据类型检查;


  • inline 函数作为类的成员函数,可以使用类的保护成员及私有成员;



inline函数使用的场合



  • 使用宏定义的地方都可以使用 inline 函数;
  • 作为类成员接口函数来读写类的私有成员或者保护成员;

为什么不能把所有的函数写成 inline 函数



  • 函数体内的代码比较长,将导致内存消耗代价;
  • 函数体内有循环,函数执行时间要比函数调用开销大;
  • 另外类的构造与析构函数不要写成内联函数。

内联函数与宏定义区别



  • 内联函数在编译时展开,宏在预编译时展开;
  • 内联函数直接嵌入到目标代码中,宏是简单的做文本替换;
  • 内联函数有类型检测、语法判断等功能,而宏没有;
  • inline 函数是函数,宏不是;
  • 宏定义时要注意书写(参数要括起来)否则容易出现歧义,内联函数不会产生歧义;

总结



  • 分享了内存管理,内存泄露,智能指针
  • 内存泄露检测工具
  • 代码中产生段错误的原因
  • 内存优化
  • 其余小知识点

欢迎点赞,关注,收藏

朋友们,你的支持和鼓励,是我坚持分享,提高质量的动力

好了,本次就到这里,*下一次 GO的并发编程分享 *

技术是开放的,我们的心态,更应是开放的。拥抱变化,向阳而生,努力向前行。

我是小魔童哪吒,欢迎点赞关注收藏,下次见~




推荐阅读
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • 本文主要解析了Open judge C16H问题中涉及到的Magical Balls的快速幂和逆元算法,并给出了问题的解析和解决方法。详细介绍了问题的背景和规则,并给出了相应的算法解析和实现步骤。通过本文的解析,读者可以更好地理解和解决Open judge C16H问题中的Magical Balls部分。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 目录在Go语言项目中使用Zap日志库介绍默认的GoLogger日志库实现GoLogger设置Logger使用LoggerLogger的运行GoLogger的优势和劣势优势劣势Ube ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • 本文介绍了一个题目的解法,通过二分答案来解决问题,但困难在于如何进行检查。文章提供了一种逃逸方式,通过移动最慢的宿管来锁门时跑到更居中的位置,从而使所有合格的寝室都居中。文章还提到可以分开判断两边的情况,并使用前缀和的方式来求出在任意时刻能够到达宿管即将锁门的寝室的人数。最后,文章提到可以改成O(n)的直接枚举来解决问题。 ... [详细]
  • Java中包装类的设计原因以及操作方法
    本文主要介绍了Java中设计包装类的原因以及操作方法。在Java中,除了对象类型,还有八大基本类型,为了将基本类型转换成对象,Java引入了包装类。文章通过介绍包装类的定义和实现,解答了为什么需要包装类的问题,并提供了简单易用的操作方法。通过本文的学习,读者可以更好地理解和应用Java中的包装类。 ... [详细]
  • 本文讨论了一个数列求和问题,该数列按照一定规律生成。通过观察数列的规律,我们可以得出求解该问题的算法。具体算法为计算前n项i*f[i]的和,其中f[i]表示数列中有i个数字。根据参考的思路,我们可以将算法的时间复杂度控制在O(n),即计算到5e5即可满足1e9的要求。 ... [详细]
  • Java自带的观察者模式及实现方法详解
    本文介绍了Java自带的观察者模式,包括Observer和Observable对象的定义和使用方法。通过添加观察者和设置内部标志位,当被观察者中的事件发生变化时,通知观察者对象并执行相应的操作。实现观察者模式非常简单,只需继承Observable类和实现Observer接口即可。详情请参考Java官方api文档。 ... [详细]
  • 本文csdn博客链接:http:blog.csdn.netscrescentarticledetails51135307本文qq空间链接:http:user.qzone.qq.com ... [详细]
  • 认真一点学 Go:18. 并发
    收录于《Go基础系列》,作者:潇洒哥老苗。>>原文链接学到什么并发与并行的区别?什么是Goroutine?什么是通道?Goroutine如何通信?相关函数的使用?sel ... [详细]
  • 按照之前我对map的理解,map中的数据应该是有序二叉树的存储顺序,正常的遍历也应该是有序的遍历和输出,但实际试了一下,却发现并非如此,网上查了下,发现从Go1开始,遍历的起始节点就是随机了,当然随机 ... [详细]
  • golang 解析磁力链为 torrent 相关的信息
    其实通过http请求已经获得了种子的信息了,但是传播存储种子好像是违法的,所以就存储些描述信息吧。之前python跑的太慢了。这个go并发不知道写的有没有问题?!packag ... [详细]
  • 本文主要分享【go协程模型】,技术文章【【GORM】模型关系-HasOne】为【VivaPython】投稿,如果你遇到GoWeb相关问题,本文相关知识或能到你。go协程模型一、概述HasO ... [详细]
  • 看到平台银行对接方案写的demo确实还不错记个笔记互相学习学习packageapiimport(cryptotlsnetnethttpstringssynct ... [详细]
author-avatar
小朋友们还记得我吗对了我就是
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有