目录
前言:
1. C/C++内存分布
2. C语言动态内存管理方式
3. C++内存管理方式
3.1 new / delete 操作内置类型
3.2 new和delete操作自定义类型
4. operator new与operator delete函数
4.1 operator new与operator delete函数
5. new和delete的实现原理
5.1 内置类型
5.2 自定义类型
6. 定位new表达式(placement-new)
malloc/free和new/delete的区别
C++
在Windows操作系统中,我们使用VS来编写C/C++代码,那编写好的代码到底是存在哪里呢?
其实,不论是我们编写好的代码,还是经过编译、链接、执行,生成的目标文件还是可执行
文件都是以文件的形式存在磁盘上的。
知道了整个代码文件存在哪里,那运行起来代码中的数据、指令代码存在哪里呢?
在之前的学习中,我们知道C/C++程序内存大致划分为4个区域:
1. 栈:又叫堆栈--一般存储:非静态局部变量/函数参数/返回值等等,栈是向下增长的。2. 堆:用于程序运行时动态内存分配,堆是可以上增长的。3. 数据段:--一般存储:存储全局数据和静态数据。4. 代码段:--一般存储:可执行的代码/只读常量。
int globalVar = 1;
static int staticGlobalVar = 1;
void Test()
{
static int staticVar = 1;
int localVar = 1;
int num1[10] = { 1, 2, 3, 4 };
char char2[] = "abcd";
const char* pChar3 = "abcd";
int* ptr1 = (int*)malloc(sizeof(int) * 4);
int* ptr2 = (int*)calloc(4, sizeof(int));
int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4);
free(ptr1);
free(ptr3);
}
1. 选择题:
选项 : A.栈 B.堆 C.数据段(静态区) D.代码段(常量区)
globalVar在哪里?____ staticGlobalVar在哪里?____
staticVar在哪里?____ localVar在哪里?____
num1 在哪里?____
char2在哪里?____ * char2在哪里?___
pChar3在哪里?____ * pChar3在哪里?____
ptr1在哪里?____ * ptr1在哪里?____
2. 填空题:
sizeof(num1) = ____;
sizeof(char2) = ____; strlen(char2) = ____;
sizeof(pChar3) = ____; strlen(pChar3) = ____;
sizeof(ptr1) = ____;
【答案】1. C C C A A A A A D A B; 2. 40 5 4 4/8 4 4/8
有问题的老铁们可以评论指出,看到我会解答!
下面这张图大家也可以看一下:
在这里顺便回顾一个知识点:
3. sizeof 和 strlen 区别?
【答】:sizeof是一个运算符,我们通常使用它来求字节数,使用方法为:sizeof()括号里面
为想要求的字节数的内容;
strlen为一个函数,我们通常使用它来求字符串的长度。
在C语言中我们通常使用malloc / calloc / realloc / free的方式来完成动态内存的开辟和释放。
在之前的博客中我有具体写到它们的使用及区别,大家可以看下:
C语言中的内存管理
C++是兼容C语言的,C++也可以使用malloc / free等方式来完成动态内存的开辟和释放。
C++又是对C语言的一种优化,在C语言的基础上提出了自己的内存管理方式:
通过new和delete操作符进行动态内存管理。
我们知道C++的数据类型分为内置类型 和 自定义类型,下面看一下它们的具体使用:
void Test()
{
//动态申请一个int类型的空间
int* p1 = new int;
//动态申请一个int类型的空间并初始化为10
int* p2 = new int(10);
//动态申请3个int类型的空间
int* p3 = new int[3];
delete p1; //释放
delete p2;
delete[] p3;
}
下面看下图解:
这里其实new和delete 与 malloc和free完成的功能差不多,都是内存的开辟和释放工作;
但是new和delete是不是比malloc和free方便很多呢?
但这里也有非常要注意的东西:
申请和释放单个元素的空间,使用new和delete操作符;
申请和释放连续的空间,使用 new[]和delete[];
注意:匹配起来使用。
补充:
其实在C++11中支持 new[] 用{}初始化;
但在C++98中,这是不支持的。
#define _CRT_SECURE_NO_WARNINGS 1
#include
using namespace std;
class A
{
public:
A(int a = 0)
:_a(a)
{
cout << "A():" << this << endl;
}
~A()
{
cout << "~A():" << this << endl;
}
private:
int _a;
};
int main()
{
// new/delete 和 malloc/free最大区别是:
// new/delete对于【自定义类型】除了开空间还会调用构造函数和析构函数
A* p1 = (A*)malloc(sizeof(A));
cout << endl << endl;
A* p2 = new A(1);
free(p1);
cout << endl << endl;
delete p2;
// 内置类型是几乎是一样的
int* p3 = (int*)malloc(sizeof(int));
int* p4 = new int;
free(p3);
delete p4;
cout << endl << endl;
A* p5 = (A*)malloc(sizeof(A) * 10);
A* p6 = new A[10];
free(p5);
delete[] p6;
return 0;
}
new对于自定义类型自动调用它的构造;
delete对于自定义类型自动调用它的析构。
new和delete对于内置类型同malloc和free功能类似:
总结:
在申请自定义类型的空间时, new 会调用构造函数, delete 会调用析构函数,而 malloc 与free 不会 。在申请内置数据类型的空间时,new/delete 和 malloc/free 完成的功能类似;
new和delete是用户进行动态内存申请和释放的操作符,operator new 和operator delete是系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间。
注意:
operator new只是完成了开辟空间的操作,
new:开辟空间+构造函数(自定义类型)构造;
这个链接很重要:一定要看!!!
operator new 和 new的关系
下面的内容了解即可:
/*
operator new:该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回;申请空间
失败,尝试执行空 间不足应对措施,如果改应对措施用户设置了,则继续申请,否
则抛异常。
*/
void* __CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{
// try to allocate size bytes
void* p;
while ((p = malloc(size)) == 0)
if (_callnewh(size) == 0)
{
// report no memory
// 如果申请内存失败了,这里会抛出bad_alloc 类型异常
static const std::bad_alloc nomem;
_RAISE(nomem);
}
return (p);
}
/*
operator delete: 该函数最终是通过free来释放空间的
*/
void operator delete(void* pUserData)
{
_CrtMemBlockHeader* pHead;
RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));
if (pUserData == NULL)
return;
_mlock(_HEAP_LOCK); /* block other threads */
__TRY
/* get a pointer to memory block header */
pHead = pHdr(pUserData);
/* verify block type */
_ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));
_free_dbg(pUserData, pHead->nBlockUse);
__FINALLY
_munlock(_HEAP_LOCK); /* release other threads */
__END_TRY_FINALLY
return;
}
/*
free的实现
*/
#define free(p) _free_dbg(p, _NORMAL_BLOCK)
通过上述两个全局函数的实现知道, operator new 实际也是通过 malloc 来申请空间 ,如果malloc 申请空间成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施就继续申请,否则就抛异常。operator delete 最终是通过 free 来释放空间的 。
如果申请的是内置类型的空间, new 和 malloc , delete 和 free 基本类似;不同的地方是: new/delete申请和释放的是单个元素的空间;new[] 和 delete[] 申请的是连续空间;而且 new 在申请空间失败时会抛异常,不用检查返回值,malloc 会返回 NULL需要检查 。
#define _CRT_SECURE_NO_WARNINGS 1
#include
using namespace std;
int main()
{
//malloc失败返回空,需要检查
char* p1 = (char*)malloc(1024 * 1024 * 1024);
if (p1 == NULL)
perror("malloc");
printf("%p\n", p1);
//new失败抛出异常,不要检查返回值
try
{
char* p2 = new char[1024*1024*1024];
printf("%p\n", p2);
}
catch (const exception& e)
{
cout << e.what() << endl;
}
return 0;
}
上述抛出及捕获异常的方法,大家需要记一下;
如果57行抛出异常,那么不用执行后续代码,直接跳到60行执行。
补充:
每个类可以去实现自己专属operator new new这个类对象,他就会调自己实现这个operator new
// 每个类可以去实现自己专属operator new new这个类对象,他就会调自己实现这个operator new
// 实现一个类专属的operator new -- 了解一下
struct ListNode
{
int _val;
ListNode* _next;
// 内存池
static allocator alloc;
void* operator new(size_t n)
{
cout << "operator new -> STL内存池allocator申请" << endl;
void* obj = alloc.allocate(1);
return obj;
}
void operator delete(void* ptr)
{
cout << "operator delete -> STL内存池allocator申请" << endl;
alloc.deallocate((ListNode*)ptr, 1);
}
struct ListNode(int val)
:_val(val)
, _next(nullptr)
{}
};
// allocator以后会讲,现在先会用即可
allocator ListNode::alloc;
int main()
{
// 频繁申请ListNode. 想提高效率 -- 申请ListNode时,不去malloc,而是自己定制内存池
ListNode* node1 = new ListNode(1);
ListNode* node2 = new ListNode(2);
ListNode* node3 = new ListNode(3);
delete node1;
//delete node2;
delete node3;
A* p1 = new A;
return 0;
}
operator new是一个封装好的函数,完成new的整个功能,其实内部也是通过malloc申请空间
的,不过在此基础之上,完成了对malloc申请失败后抛出异常的处理,使得new申请失败后
会抛出异常。
在使用new时,调用全局的operator new 和 类专属的operator new的区别:
调用全局的:每次调用每次向堆区申请空间,效率不高;
调用类专属的:有一个内存池,每次申请时申请内存池里面的内存空间,内存池没有了,一
下向堆区申请多个空间存在内存池里,再从内存池申请空间,效率较高;
1. 调用 operator new 函数申请空间;2. 在申请的空间上执行构造函数,完成对象的构造;
1. 在空间上执行析构函数,完成对象中资源的清理工作;2. 调用 operator delete 函数释放对象的空间
1. 调用 operator new[] 函数,在 operator new[] 中实际调用 operator new 函数完成 N 个对象空间的申请;2. 在申请的空间上执行 N 次构造函数;
1. 在释放的对象空间上执行 N 次析构函数,完成 N 个对象中资源的清理;2. 调用 operator delete[] 释放空间,实际在 operator delete[] 中调用 operator delete 来释放空间
定位new 表达式:是在 已分配的原始内存空间中调用构造函数初始化一个对象。
new (place_address) type 或者new (place_address) type(initializer-list)
//place_address必须是一个指针,initializer-list是类型的初始化列表
定位new表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化,所以如果是自定义类型的对象,需要使用new的定义表达式进行显示调构造函数进行初始化。
class A
{
public:
A(int a = 0)
: _a(a)
{
cout << "A():" << this << endl;
}
~A()
{
cout << "~A():" << this << endl;
}
private:
int _a;
};
// 定位new/replacement new
int main()
{
// p1现在指向的只不过是与A对象相同大小的一段空间,还不能算是一个对象,因为构造函数没有执行
A* p1 = (A*)malloc(sizeof(A));
// 注意:如果A类的构造函数有参数时,此处需要传参
new(p1)A;
p1->~A();
free(p1);
A* p2 = (A*)operator new(sizeof(A));
new(p2)A(10);
p2->~A();
operator delete(p2);
return 0;
}
总结:
对于自定义类型:
new:开辟空间+构造;
也就是说:
operator new 与 定位new 联合起来就是new的整个功能。
malloc/free 和 new/delete 的共同点是:都是从堆上申请空间,并且需要用户手动释放。不同的地方是:1. malloc 和 free 是函数, new 和 delete 是操作符;2. malloc 申请的空间不会初始化, new 可以初始化;3. malloc 申请空间时,需要手动计算空间大小并传递, new 只需在其后跟上空间的类型即可,如果是多个对象, [ ] 中指定对象个数即可;4. malloc 的返回值为 void*, 在使用时必须强转, new 不需要,因为 new 后跟的是空间的类型;5. malloc 申请空间失败时,返回的是 NULL ,因此使用时必须判空, new 不需要,但是 new 需要捕获异常;6. 申请自定义类型对象时, malloc/free 只会开辟空间,不会调用构造函数与析构函数,而new在申请空间后会调用构造函数完成对象的初始化,delete 在释放空间前会调用析构函数完成空间中资源的清理
坚持打卡!