前言
笔者最近在阅读grpc
的cpp
部分源码时,发现了一种笔者未见过的new
用法,是笔者阅读甚少了。借此机会,分享给大家,以笔者浅薄的理解,简单探讨一下new
内存分配及相关的理论。
接下来便以示例代码做演示。
示例代码
#include <iostream>
#include <new>
#include <string>
#include <memory>
#include <limits.h>
#pragma pack(1)
class Test {
public:
Test(std::string& name) : name_(name) { }
virtual ~Test() {}
private:
std::string name_;
};
int main(int argc, char *argv[])
{
uint8_t mem[sizeof(Test)];
/* 一般用法 */
Test *t1 = new Test(std::string("test1"));
/* 二般用法,在指定内存区域初始化对象 */
Test *t2 = new (mem) Test(std::string("test2"));
delete t1;
return 0;
}
如上面的代码所示,上面的new
用法有两种:
- 一种是常用的用法,其实包含了
两个步骤
,即先分配内存
,然后调用类构造函数初始化对象
。 - 另外一种用法是使用
new
对指定的内存
调用类的构造函数进行对象布局的初始化,这种初始化需要保证目标内存块大小
需>=
类对象的内存大小,包含内存对齐
。
内存分配
除此之外,也分享一下关于malloc | free
、new | delete
的简单认识:
malloc | free
是C
语言提供的内存分配函数。new | delete
属于cpp
里面提供的内存操作符。malloc
分配的内存是未初始化的。new
分配的内存是已初始化的,因为new
其实是先分配内存
然后再初始化
。malloc
返回的是void*
指针,需要void*
强制转换至目标类型指针
。new
返回的是目标类型指针
。new | delete
其实是基于malloc | free
来实现的。
new
步骤:
- 使用全局的
new
分配指定内存,以类为例,会分配相应对象大小的内存; - 以对象为例,在
步骤1
分配的内存调用类的构造函数
进行对象初始化;
delete
步骤(其实是调用类的删除析构函数
,删除析构函数包含下面两个步骤):
- 调用类的
析构函数
,销毁对象布局。 - 调用全局的
delete
释放指定内存。
malloc | free
的实现:
基于linux
操作系统而言,在笔者阅读过的早期linux
内核源码(0.11 或 0.96版本
),操作系统层面的内存管理中,内存是以页的形式组织,内存页的大小一般为4096
字节,将页内存划分成各种不同大小的内存块,对应大小的内存块挂到对应链表头,以链表形式组织。形式如下图:
-
调用
malloc
时,根据所需内存大小找到刚好满足需求(当前链表头内存块大小
>=所需内存大小
)的链表头,再遍历链表找到第一块空闲的内存块。 -
free
时,根据传入的地址,换算得到对应内存页的地址,根据内存页找到对应的内存块描述结构体,将当前内存块挂到全局的空闲内存链表上,如果当前释放地址对应的内存页的引用计数
为0,则将当前内存页的内存释放。 -
关于
malloc
,则需要聊下glibc
库的malloc
。glibc
提供的malloc
分配内存时,其实是分配了块大小略微大于所需的内存块
,在整个内存块的前面
有一块内存
记录着当前所分配内存的大小
,返回
给用户的地址其实是glibc
拿到的内存块的地址稍微偏后
的内存地址,这也就是为什么free
时只需传入释放内存
的地址而不需要传入内存块大小
就能释放内存的原因。 -
前面所述关于操作系统的内存管理是内核比较古老的版本(
0.11 或 0.96 版本
),与现代linux
操作系统的内存管理细节上估计有所不同,现代linux
操作系统已引入了虚拟内存
、段页式内存管理
等等。 -
笔者认为,学习早期
linux
内核,是一种让你快速理解操作系统相关原理的方式。早期linux
内核源码代码量较少,易于阅读理解。 -
关于古老版本的
内核源码
可从赵炯博士
维护的oldlinux
网站获取,网站地址http://www.oldlinux.org/
或点击链接。学习linux内核时,可搭配赵炯博士
写的linux内核源码剖析
来看。
结尾
- 行文至此,关于
new
的分享已结束了。若有错误之处,欢迎指正。 - 如果觉得还不错,你的
点赞关注加收藏
便是笔者继续更新
的最大动力哦^_^
。