作者前言
🎂 ✨✨✨✨✨✨🍧🍧🍧🍧🍧🍧🍧🎂
🎂 作者介绍: 🎂🎂
🎂 🎉🎉🎉🎉🎉🎉🎉 🎂
🎂作者id:老秦包你会, 🎂
简单介绍:🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂
喜欢学习C语言、C++和python等编程语言,是一位爱分享的博主,有兴趣的小可爱可以来互讨 🎂🎂🎂🎂🎂🎂🎂🎂
🎂个人主页::小小页面🎂
🎂gitee页面:秦大大🎂
🎂🎂🎂🎂🎂🎂🎂🎂
🎂 一个爱分享的小博主 欢迎小可爱们前来借鉴🎂
内存划分
内存的划分是为了满足不同的数据,有不同的存储需求,各区域满足不同的需求,
需要注意的是,代码段是不能被修改的,如果是强行修改就是这样
我们来做一道题
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);
}
前五个很简单,答案是C CC AA
后五个的答案是 AAADAB
我们要注意的是static修饰局部变量(初始化一次,不消毁)、全局变量(只能在本文件内使用,不能跨文件使用)、函数(只能在本文件内使用,不能跨文件使用,
还要const 修饰一些变量比如:
const int a = 10; a变量是常变量,
const int* p = (int)malloc(4);const修饰的是p不能更改,
上面的
const char pChar3存储的地址是常量区的
这里的答案是 40 5 4/8 5 4 4/8
C++内存管理方式
前面我们学习了C语言的动态内存开辟,那c++也有自己的动态内存开辟
通过new和delete操作符进行动态内存管理。
#include <iostream>
using namespace std;
int main()
{
//动态内存开辟
int* p = new int;//一个整形空间
int* p1 = new int[10];//十个整形空间
//初始化
int* p2 = new int(1);
int* p3 = new int[10]{1, 2, 3};
//释放
delete p;
delete[] p1;
delete p2;
delete[] p3;
return 0;
}
主要是为了解决动态申请自定义类型对象初始化问题
#include <iostream>
using namespace std;
class ListNode
{
public:
ListNode(int a = 30)
{
_a = a;
_Node = nullptr;
}
void LinkNode(const ListNode& newnode)
{
_Node = newnode._Node;
}
private:
int _a;
ListNode* _Node;
};
int main()
{
ListNode* p1 = new ListNode;
ListNode* p = new ListNode(60);//可以这样初始化
ListNode* p2 = new ListNode[3]{ 6,5,4 };//这里是隐式类型转换,也可以使用匿名对象
p1->LinkNode(*p);
return 0;
}
new的本质:开空间+调用构造函数初始化,
但是使用new来开辟很大空间也会失败,这个异常必须捕获,不然就会有意外
异常捕获
int main()
{
char* p = new char[0x7ffffff];
cout << p << endl;
}
会输出很多东西,无限循环,但是没有报错
int main()
{
//异常捕获
try//如果try里面报错了就会运行catch里面的内容
{
char* p = new char[0x7ffffff];
cout << (void*)p << endl;
char* p2 = new char[1024*1024*1024];
cout << (void*)p2 << endl;
}
catch (const exception& e)
{
cout << e.what() << endl;
}
}
格式
try
{}
catch (const exception& e)
{}
释放空间
delete p;//使用于创建一个的时候
delete[] p;//使用于创建多个并且连续的
malloc/free和new/delete的区别
#include<iostream>
using namespace std;
class Stack
{
public:
Stack(int capacity)
{
_top = 0;
_a = new int[capacity];
_capacity = capacity;
}
~Stack()
{
cout << _capacity << endl;
delete[] _a;
_a = nullptr;
_top = 0;
_capacity = 0;
}
private:
int* _a;
int _top;
int _capacity;
};
int main()
{
Stack s1(30);
Stack* p = new Stack(40);
//delete p;
free(p);
return 0;
}
会发现,无法调用析构函数,是因为free只会释放当前的空间,不会释放_a指向的空间,除非调用析构函数,但是free释放了当前的空间,无法找到哪个空间在哪里,就会出现内存泄漏
如果我们使用delete就会发现,会调用析构函数,
new和delete的底层
/*
operator new:该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回;申请空间
失败,尝试执行空间不足应对措施,如果改应对措施用户设置了,则继续申请,否则抛异常。
*/
void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{
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)
new和delete是用户进行动态内存申请和释放的操作符,operator new 和operator delete是
系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过
operator delete全局函数来释放空间。需要注意的是这里的operator new 和operator delete只和申请空间有关,跟调用析构和构造函数没有关系
为啥不直接调用malloc和free来呢
我们知道malloc开辟失败返回0,c++的自定义类型的对象,创建失败是不能返回0的,所以要抛异常才能符合这个特性,所以这里operator new 和operator delete是为抛异常做的准备的
下面是vs反汇编调用函数,以delete为例:
所以我们可以看作
new[]和delete[]
那我们来看看 new[]这个操作符
我们利用上面的stack自定义类型为例,开辟10个stack类型的空间,
大概是申请了10个Stack对象的地址,然后再调用10次构造函数
我们来看看其反汇编
当我们查看size的大小的时候
会发现多了4个字节,
原因如图:开辟多出来的用来存储对象个数,主要是为了delete操作符准备的.因为我们释放多个对象,我们不用写个数,也就是这个原因,
如果我们创建多个对象,却只释放一个,就会程序崩溃,为啥不是泄漏呢?
因为我们知道创建多个对象,会多开辟4个字节来存储,而开辟一个是不会浪费四个字节的,所以,也就是创建多个对象,只释放一个,是位置释放错了,因为创建一个对象返回的地址就是起始地址,如果创建的是多个对象的,返回的地址不是起始地址,
需要注意的是,如果类的成员只有内置类型,析构函数可要可不要,使用free释放或者delete都不会报错,
malloc/free和new/delete的共同点是:都是从堆上申请空间,并且需要用户手动释放。不同的地
方是:
- malloc和free是函数,new和delete是操作符
- malloc申请的空间不会初始化,new可以初始化
- malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可,
如果是多个对象,[]中指定对象个数即可 - malloc的返回值为void*, 在使用时必须强转,new不需要,因为new后跟的是空间的类型
- malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需
要捕获异常 - 申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new
在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成
空间中资源的清理
定位new表达式(placement-new)
我们知道构造函数不能显示调用,在创建对象的时候会自动调用构造函数,但是析构函数是可以的
如果我们想要进行显示调用构造函数,可以通过定位new来实现,
定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象。
使用格式:
new (place_address) type或者new (place_address) type(initializer-list)
place_address必须是一个指针,initializer-list是类型的初始化列表
#include<iostream>
using namespace std;
class Stack
{
public:
Stack(int capacity = 5)
{
_top = 0;
_a = new int[capacity];
_capacity = capacity;
}
~Stack()
{
cout << _capacity << endl;
delete[] _a;
_a = nullptr;
_top = 0;
_capacity = 0;
}
private:
int* _a;
int _top;
int _capacity;
};
int main()
{
Stack* p = new Stack(10);
new(p)Stack(20);//显示调用构造函数
delete p;
return 0;
}