侯捷 C++ 课程学习笔记:C++内存管理机制

1、内存分配每一层面

C++ Applications->
C++ Library(std::allocator)->
C++ primitives(new,new[],new(),::operator new(),...)->
CRT(malloc/free)->
O.S.API(such as HeapAlloc,VirtualAlloc,...)用法:
void* p1 = malloc(512);	// 512bytes
free(p1);void* p2 = ::operator new(512);	// 512bytes
::operator delete(p2);::operator new()底层实现就是调用了malloc。
::operator delete()底层实现就是调用了free。#ifdef __MSC_VERint* p3 = allocator<int>().allocate(3, (int*)0);	// 分配3个intsallocator<int>().deallocate(p3,3);
#endif
#ifdef __GNUC__void* p4= alloc::allocate(512);	// 512bytes.旧版本G2.9alloc::deallocate(p4,512);void* p5 = allocator<int>().allocate(3);	// 分配3个ints.新版本allocator<int>().deallocate((int*)p5,3);void* p6 = __gnu_cxx::__pool_alloc<int>().allocate(3);	// 分配3个ints.新版本__gnu_cxx::__pool_alloc<int>().deallocate((int*)p6,3);
#endif方式p5和方式p6是gnuc的两种分配器方式,事实上gnuc有七八个分配器。

2、C++ 表达式new和delete不可重载,C++ 函数::operator new()和::operator delete()可以重载。

3、表达式new实际上是做了下面3个语句的事情:

Complex* pc = new Complex(1,2);
编译器转为:
Complex* pc;
try
{void* mem = operator new(sizeof(Complex));	// operator new里面调用的就是mallocpc = static_cast<Complex*>(mem);pc->Complex::Complex(1,2);	// 注意只有部分编译器(VC6)可以这样调用ctor.
}
catch(std::bad_alloc)
{// 若allocation失败就不执行ctor
}注:如果要直接调用ctor,可运用placement new:
new(p)Complex(1,2);

4、表达式delete实际上是做了下面2个语句的事情:

delete pc;
编译器转为:
pc->~Complex();			// 先调用析构函数
operator delete(pc);	// 然后再释放内存。operator delete里面调用的就是free

5、array new

Complex* pca = new Complex[3];
调用3次ctor(默认构造函数)。
无法给参数初始值。

6、array delete

delete[] pca;
调用3次dtor。
  • 如果没有写中括号,只会调用1次dtor。

  • 对于class without ptr member可能没影响。

  • 对于class with pointer member通常有影响。

  • 如果array new的是一个non-trivial dtor的类,则pca指向的地址就是一个元素的地址;

  • 否则,如果new的是一个trivial dtor的类,编译器会在放元素的前面用一个4个字节记录数组的大小,则当delete pca的时候,

  • 编译器不知道delete pca原来是需要delete掉数组的,那么此时编译器去delete掉记录了数组大小的那4个字节就会有问题。

  • vc debug环境下会运行时报错。

7、placement new的一种用法:

class A
{
public:int id;A() : id(0) {}A(int i) : id(i) {}~A() {}
}A* buf = new A[3];	// 调用了3次ctor A()
A* tmp = buf;// 在tmp所在的位置调用ctor(在这个定点new上创建对象)
for(int i=0; i<3; ++i)new(tmp++)A(i);		// 调用了3次ctor A(int i)

8、placement new允许我们将object构建于allocated memory中。

没有所谓的placement delete,因为placement new根本没有分配memory。

char* buf = new char[sizeof(Complex)*3];
Complex* pc = new(buf)Complex(1,2);
编译器转为:
Complex* pc;
try
{void* mem = operator new(sizeof(Complex), buf);	// operator new()里面啥也没做,直接返回bufpc = static_case<Complex*>(mem);pc->Complex::Complex(1,2);
}
catch(std::bad_alloc)
{// 若allocation失败就不执行ctor
}注意,关于 placement new 或指new(p),或指::operator new(size,void*);

9、placement new 也可以重载。

  • 我们可以重载class member operator new(),写出多个版本,前提是每一版本的声明都必须有独特的参数列,
    其中第一参数必须是size_t,其余参数以new所指定的placement arguments为初值。
    出现于new(…)小括号内的便是所谓placement arguments。

  • 我们也可以(并不是必须,所以也可以没有)重载class member operator delete(),写出多个版本。但它们绝不会被delete调用。
    只有当new所调用的ctor抛出exception,才会调用这些重载版的operator delete()。
    它只可能这样被调用,主要用来归还未能完全创建成功的object所占用的memory。

  • 即使operator delete(…)未能一一对应于operator new(…),也不会出现任何报错(VC6会有警告)。(编译器认为你是要放弃处理ctor发出的异常)

10、placement new在标准库string的一个使用例子——basic_string使用new(extra)扩充申请量:

template<...>
class base_string
{inline static void* operator new(size_t, size_t);	// 第一个参数是类的大小,必须的。第二个是placement new的参数
}xxxx xxxxx::operator new(size_t s, size_t extra)
{return Allocator::allocate(s + extra*sizeof(charT));
}size_t extra = xxxxx;
Rep *p = new(extra)Rep;		// 此处传参extra正是传给operator new的第二个参数extra那块内存正是string用来做引用计数的。

11、连续创建几个只有8个字节大小的对象,如果使用自己预先申请到的一大片内存的方式,那就没有cookie(但这大块内存的上下还是有cookie的),每个对象的地址间隔(一般,因为malloc拿到的内存可能是分散的)是8字节。

但是如果是没有使用自己的内存池,那么每个对象的地址间隔是16字节,在内存布局上是上下各多了4个字节的cookie。

12、设计内存池时的一种巧妙的手法——这种技术称为embedded pointers(嵌入式指针):

class A*
{
private:struct ARep{unsigned long miles;char type;}
private:union			// sizeof(A)是8字节 {ARep rep;	// 8个字节A* next;	// 4个字节,所以只要union中的前4个字节当做指针来用}
}

在operator new()里面用union里面的next来存放下一个内存块的地址,返回一个内存块的地址时,外面又可以在这块内存存放数据。(假设这里的内存池是用链表来设计的)

常见的类都是大于等于4个字节的,因此借用前4个字节来当链表指针用是没有问题的。

上面的版本独立出allocator来就是:

class allocator
{
private:struct obj{struct obj* next;		}
}另外,
void* A::operator new(size_t size)
{if(size != sizeof(A))		// 当有继承的时候,大小不等return ::operator new(size);...
}

13、macro for static allocator

#define IMPLEMENT_POOL_ALLOC(class_name) \ 
allocator class_name::myAlloc;class Foo
{...
}
IMPLEMENT_POOL_ALLOC(Foo)

14、当operator new没能力为你分配出你所申请的memory,会抛出一个std::bad_alloc exception。可以令编译器这么做:

new(nothrow)Foo;

C++会在抛出exception之前会先(不止一次)调用一个可由client指定的handler,以下是new handler的形式和设定方法:

typedef void(*new_handler)();
new_handler set_new_handler(new_handler p)throw();

设计良好的new handler只有两个选择:

  • 让更多的memory可用
  • 调用abort()或exit()
void noMoreMemory()
{abort();
}void main()
{set_new_handler(noMoreMemory);
}

15、=default、=delete

  • operator new:不支持default、支持delete
  • operator new[]:支持delete
  • operator delete:不支持default、支持delete
  • operator delete[]:支持delete

这里说的支持,是指类这样定义能编译通过,但是使用该类的时候还是用不了。

16、标准库std::allocator的实现每个编译器厂家可能都不一样。

  • VC6的allocator,内部没有做任何的内存管理,只是以::operator new和::operator delete完成allocate()和deallocate(),没有任何特殊设计。

  • BC5的allocator只是以::operator new和::operator delete完成allocate()和deallocate(),没有任何特殊设计。

  • G2.9的allocator只是以::operator new和::operator delete完成allocate()和deallocate(),没有任何特殊设计。
    G2.9容器使用的分配器,不是std::allocator而是std::alloc。

  • G4.9标准库中有许多extended allocators,其中__gnu_cxx::__pool_alloc就是G2.9的alloc。
    allocator是标准分配器。

  • G4.9的allocator只是以::operator new和::operator delete完成allocate()和deallocate(),没有任何特殊设计。

17、mingw5.3.0中的std::map源码跟踪如下:

template <typename _Key, typename _Tp, typename _Compare = std::less<_Key>,typename _Alloc = std::allocator<std::pair<const _Key, _Tp> > >
class map
{...
}template<typename _Tp>
class allocator: public __allocator_base<_Tp>
{...
}template<typename _Tp>
using __allocator_base = __gnu_cxx::new_allocator<_Tp>;template<typename _Tp>
class new_allocator
{...// NB: __n is permitted to be 0.  The C++ standard says nothing// about what the return value is when __n == 0.pointerallocate(size_type __n, const void* = 0){ if (__n > this->max_size())std::__throw_bad_alloc();return static_cast<_Tp*>(::operator new(__n * sizeof(_Tp)));}// __p is not permitted to be a null pointer.voiddeallocate(pointer __p, size_type){ ::operator delete(__p); }...
}

18、G2.9 std::alloc运行模式

  • 16条链表(称为freelist-自由链表),每条的节点内存大小间隔8个字节。所以,第一条链表节点内存大小是8字节,最后一条是128字节。

  • 标准库每次申请一大块内存是20size。
    其实是20
    2size+RoundUp(累计申请量>>4),但是只用了前20个,后20个的话是备用(其实是先全部放入备用池,然后再取20个出来用)。
    备用是留给下一次申请内存的时候用,但不一定是当前那条链表。当备用用完后,下一次再申请内存的时候又是20
    2size。
    另外,当备用内存很多,而申请的内存很小的时候,则申请20个,因为申请的个数是1-20之间。RoundUp(累计申请量/16)是追加量,上调至16的倍数。
    当备用内存很少,而申请的内存很大(备用内存不足以分配一个)的时候,会先将备用池的剩余量(碎片)分配给对应大小的链表。然后备用池剩余量为0了,再去申请20
    2*size+RoundUp(累计申请量>>4)的内存。

  • 当系统内存不足时,alloc先会从手中资源最接近申请内存大小(即仅比申请大小大一点点的有空闲内存个数的链表)的链表中回填一个内存给备用池,然后再从备用池中切出一块给申请内存的用户。
    比申请大小大的所有链表都没有空闲的内存个数时,则分配内存失败。

  • volatile关键字用于多线程。

19、VC6的malloc

SBH:Small Block Heap

VC6里面有小区块内存的内存管理。VC10不再有,因为已经搬到操作系统的HeapAlloc函数里面去了。

初始化:mainCRTStartup() -> _heap_init() -> __sbh_heap_init()

第一次分配内存:mainCRTStartup() -> _ioinit() -> malloc()/_malloc_dbg() -> _heap_alloc_dbg() -> _heap_alloc_base() -> _sbh_alloc_block()

_malloc_dbg是debug模式下调用的申请内存的函数,有额外的debug header开销(夹着一个_CrtMemBlockHeader结构体)。

debug模式下:所有经过malloc拿到的内存块,都被sbh登记起来(链表串起来)。同时也正因为debug header的存在,debug模式很厉害,可以追踪内存的变化等等。

debug header里面有个字段是记录这内存是用来干什么的,比如在main函数正式开始前的那些叫CRT_BLOCK,main函数过程中的是NORMAL_BLOCK,则main函数结束的那一刻,如果还有标记为NORMAL_BLOCK的内存块(但此时还有标记为CRT_BLOCK的内存块),则就是内存泄漏了。

SBH的内存管理其实就是分段管理,每段32KB。分段管理的好处是利于归还给操作系统,归还的时候可以归还某一段。
每段32KB,分为8个page,每个页4KB。

GCC的malloc和VC6的malloc的设计大概差不多,都是有上下cookie。

20、内存块上下cookie的作用之一:上下内存块的合并。

21、gcc allocator的内存管理不是为了速度快,是为了去除cookie。malloc的速度已经够快了,因为malloc内部实现也是个内存池。

22、loki allocator的内存回收与VC6的malloc内存回收都有延缓归还内存的设计。

loki allocator使用“以array取代list,以index取代pointer”的特殊实现手法。

loki allocator能够以很简单的方式判断“chunk全回收”进而将memory归还给操作系统。(记录可用区块的个数是否等于总个数)

23、VS2013的标准分配器

  • deallocate()其实是直接调用::operator delete()
  • allocate()其实是直接调用::operator new()

24、G4.9的标准分配器

  • deallocate()其实是直接调用::operator delete()
  • allocate()其实是直接调用::operator new()

25、G4.9的malloc_allocator

  • deallocate()其实是直接调用std::free()
  • allocate()其实是直接调用std::malloc()

头文件在./ext/malloc_allocator.h

26、G4.9的array_allocator

  • array_allocator并不会回收已给出的内存空间。
  • array_allocator的deallocate()啥也没做。

27、G4.9的debug_allocator

附带额外的空间用以记录整个内存块的大小。

28、G2.9分配器那套称为SGI style的,因为那一套是从SGI买过来的。

欲使用std::allocator以外的allocator,必须自行#include<ext/...>
例如:#include <ext/pool_allocator.h>
vector<int, __gnu_cxx::__pool_alloc<int>> vecPool;

29、G4.9的bitmap_allocator

头文件在./ext/bitmap_allocator.h

为一次只要一个元素大小内存的容器服务(其实所有容器都是一次只要一个元素)。

bitmap_allocator的每一大块(super block)有类型的定义,即不同的value type即使大小相同也不混用。

bitmap_allocator也有延缓归还内存的设计,当超过64个空闲块的时候,归还最大的那块。

30、const member functions(常量成员函数),const告诉编译器该函数只读成员数据。

const对象不能调用非const成员函数。所以当一个成员函数明明是不会改动data members的,却没有修饰为const时,用const对象去调用就会报错,但这不应该。

const对象其data members不得改动。

当成员函数的const和non-const版本同时存在,const object只会(只能)调用const版本,non-const object只会(只能)调用non-const版本。

函数重载是不管return type的,但const是函数签名的一部分。

在设计类的时候,不改变data members的成员函数应该加const。

31、标准库string的拷贝(里面有引用计数)也是共享一份的、copy on write的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/43833.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2.pycharm部署Ai - 编程好助手

一、pycharm安装continue插件 1.提前安装好pycharm&#xff0c;并双击打开 2.File – Setting 3.Plugins – 搜索Continue &#xff0c; 点击Install安装 4.点ok 二、获取硅基流动API 1.登入网站&#xff1a;https://siliconflow.cn/zh-cn/#/&#xff0c;并注册登入 2.获取AP…

《数据结构:单链表》

“希望就像星星&#xff0c;或许光芒微弱&#xff0c;但永不熄灭。” 博主的个人gitee&#xff1a;https://gitee.com/friend-a188881041351 一.概念与结构 链表是一种物理存储上非连续、非顺序的存储结构&#xff0c;数据元素的顺序逻辑是通过链表中的指针链接次序实现的。 单…

Visual Studio 2019 Qt QML 项目环境搭建常见问题处理方法

在 Visual Studio 2019 运行 Qt/QML 项目比直接使用QtCreator环境麻烦&#xff0c;主要是有qmake 的一些配置项不能在 Visual Studio中设置。下面整理一些常见问题的处理方法&#xff0c;供参考&#xff1a; 搭建VS Qt 环境&#xff0c;在Visual Studios 2019下面安装 Qt Vis…

基于Spring Boot的网上商城系统的设计与实现(LW+源码+讲解)

专注于大学生项目实战开发,讲解,毕业答疑辅导&#xff0c;欢迎高校老师/同行前辈交流合作✌。 技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;…

如何监控和优化服务器的 CPU 性能

一、实时监控与诊断工具 1. 核心监控工具 bash 复制 # 实时CPU使用率监控 top -H -p <PID> # 按线程查看CPU占用 htop --sort-keyPERCENT_CPU # 可视化进程CPU排序 mpstat -P ALL 1 # 每核心使用率统计 2. 上下文切换分析 bash 复制 pidstat -w…

【蓝桥杯14天冲刺课题单】Day 1

1. 题目链接&#xff1a;19937 艺术与篮球 该题目的难点主要在20240413这个日期需要结束程序跳出循环。最开始将该输出ans的位置放在了for循环之外&#xff0c;此时的日期已经循环完了2024年所有的日期&#xff0c;则最后会统计多而导致结果错误。 AC代码&#xff1a; #incl…

Masked Attention 在 LLM 训练中的作用与原理

在大语言模型&#xff08;LLM&#xff09;训练过程中&#xff0c;Masked Attention&#xff08;掩码注意力&#xff09; 是一个关键机制&#xff0c;它决定了 模型如何在训练时只利用过去的信息&#xff0c;而不会看到未来的 token。这篇文章将帮助你理解 Masked Attention 的作…

Arduino、ESP32驱动GUVA-S12SD UV紫外线传感器(光照传感器篇)

目录 1、传感器特性 2、控制器和传感器连线图 3、驱动程序 UV紫外线传感器是一个测试紫外线总量的最佳传感器,它不需要使用波长滤波器,只对紫外线敏感。 Arduino UV紫外线传感器,直接输出对应紫外线指数(UV INDEX)的线性电压,输出电压范围大约0~1100mV(对应UV INDEX值…

基于微波光子信道的图像传输系统matlab仿真,调制方式采用OFDM+QPSK,LDPC编译码以及LS信道估计

目录 1.算法仿真效果 2.算法涉及理论知识概要 2.1 OFDMQPSK原理 2.2 微波光子信道简介 3.MATLAB核心程序 4.完整算法代码文件获得 1.算法仿真效果 matlab2022a仿真结果如下&#xff08;完整代码运行后无水印&#xff09;&#xff1a; 仿真操作步骤可参考程序配套的操作视…

【黑马点评】Redis解决集群的session共享问题

由于不同的tomcat服务器之间的session是不共享的&#xff0c;当请求如果在不同tomcat服务器之间切换就会导致数据丢失的问题。 使用redis可以解决session数据共享的问题 redis是tomcat以外的存储&#xff0c;存在redis中的数据&#xff0c;任何一台tomcat都能看得见&#xff0…

淘宝客户端动态化页面搭建

在手机淘宝等高频更新的业务场景中&#xff0c;UI页面的动态化和快速交付成为技术团队面临的重要挑战。本文围绕“客户端动态化页面搭建”这一主题&#xff0c;深入探讨了如何通过抽象框架设计解决高动态化页面的快速构建问题。文章详细介绍了框架的核心模块&#xff08;如Data…

无人机,雷达定点飞行时,位置发散,位置很飘,原因分析

参考&#xff1a; 无人车传感器 IMU与GPS数据融合进行定位机制_gps imu 组合定位原始数-CSDN博客 我的无人机使用雷达定位&#xff0c;位置模式很飘 雷达的更新频率也是10HZ&#xff0c; 而px飞控的频率是100HZ&#xff0c;没有对两者之间的频率差异做出处理 所以才导致无人…

Postman 版本信息速查:快速定位版本号

保持 Postman 更新至最新版本是非常重要的&#xff0c;因为这能让我们享受到最新的功能&#xff0c;同时也保证了软件的安全性。所以&#xff0c;如何快速查看你的 Postman 版本信息呢&#xff1f; 如何查看 Postman 的版本信息教程

Object结构

Object结构 参考博客

数据分析概述

数据分析&#xff1a;用适当的分析方法和挖掘方法对收集来的数据进行研究总结&#xff0c;提取有用的信息&#xff0c;形成结论并支持决策的过程。 一.数据分析的分类 1.业务描述性分析。以数据为分析对象&#xff0c;以探索数据内的有用信息为主要途径&#xff0c;以解决业务…

ubuntu下终端打不开的排查思路和解决方法

问题现象描述&#xff1a;ubuntu开机后系统桌面显示正常&#xff0c;其他图形化的app也都能打开无异常&#xff0c;唯独只有terminal终端打不开&#xff0c;无论是鼠标点击终端软件&#xff0c;还是ctrlaltt&#xff0c;还是altF2后输入gnome-terminal后按回车&#xff0c;这三…

第一天 Linux驱动程序简介

目录 一、驱动的作用 二、裸机驱动 VS linux驱动 1、裸机驱动 2、linux驱动 三、linux驱动位于哪里&#xff1f; 四、应用编程 VS 内核编程 1、共同点 2、不同点 五、linux驱动分类 1、字符设备 2、块设备 3、网络设备 六、Linux驱动学习难点与误区 1、学习难点 …

探索抓包利器ProxyPin,实现手机APP请求抓包,支持https请求

以下是ProxyPin的简单介绍&#xff1a; - ProxyPin是一个开源免费HTTP(S)流量捕获神器&#xff0c;支持 Windows、Mac、Android、IOS、Linux 全平台系统- 可以使用它来拦截、检查并重写HTTP(S)流量&#xff0c;支持捕获各种应用的网络请求。ProxyPin基于Flutter开发&#xff0…

Windows中安装git工具

下载好git安装包 点击next 选择安装目录 根据需要去勾选 点击next 点击next PATH环境选择第二个【Git...software】即可&#xff0c;再点击【Next】。 第一种配置是“仅从Git Bash使用Git”。这是最安全的选择&#xff0c;因为您的PATH根本不会被修改。您只能使用 Git Bash 的…

Banner区域

div下 justify-content:space-between 左侧测导航left 在这里插入图片描述 在这里插入图片描述