【C++】C++的内存管理

简介: 【C++】C++的内存管理

内存分布

栈:存放非静态局部变量,函数参数,返回值等。栈是向下增长的。

堆:用于动态内存分配,堆是向上增长的。

数据段:存放全局数据,静态数据。

代码段:可执行代码,只读常量

内存映射段:是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可以使用系统接口创建共享内存,做进程间通信

示意图

 

下面代码中的变量都在哪里呢

 

int a = 0;
static int b = 0;
 
void Teat()
{
  int c = 0;
 
  static int d = 0;
 
  int num[10] = { 0 };
 
  char char2[] = "abcd";
 
  const char* pc = "abcd"; 
 
  int* ptr = (int*)malloc(sizeof(int) * 4);
 
  free(ptr);
}

解析


动态内存管理

C语言的动态内存管理

void * malloc(size_t size)
在堆上开辟空间
开辟失败返回NULL
返回值要检查是否为空
接受返回值需要强转
void * calloc(size_t num, size_t size)
再堆上开辟num个size大小的空间,并把该空间初始化为0
开辟失败返回NULL
返回值要检查是否为空
接受返回值需要强转
void * realloc(void* ptr, size_t size) // (动态内存空间, 新空间的大小)
调整原有空间
调整失败返回NULL
不会初始化空间
用新的指针接收

C++的动态内存管理

C++兼容C语言,C语言的动态内存管理方式依然在C++中能用。但在C++的大部分场景中,C语言的动态内存管理方式并不适用。

C++引入了两个关键字

new:在堆上开辟空间

delete:释放new开辟的空间

对内置类型操作

对于内置类型而言,new和delete与C语言的动态内存管理基本一致

开辟一个int类型的空间

int* ptr = new int;

开辟一个int类型的空间,并初始化为0

int* ptr = new int(0);

释放空间

delete ptr;

开辟10个int类型的空间,并初始化

int* ptr = new int[10]{1, 2, 3, 4, 5, 6};

后面会自动补0

释放该空间

delete[] ptr;

注意:new和delete匹配,new[]和delete[]匹配。如果不匹配会怎么样,对内置类型来说最多是警告。

对自定义类型操作

引入new和delete就是为了应对自定义类型

new在堆上开辟空间时,还会调用构造函数

delete释放堆上的空间时,还会调用析构函数


小编会用下述代码带大家感受一下这两点

class Date //日期类
{
public:
 
  Date() //构造函数
  :_a(new int[7]{0}) //为_a开空间   初始化列表
  ,_year(2024)
  ,_month(4)
  ,_day (24)
  {
    
    cout << "构造函数" << endl;
  }
  ~Date() //析构函数
  {
    delete[] _a;  //释放_a的空间
    cout << "析构函数" << endl;
 
  }
 
private:
  int _year;  //年
  int _month; //月
  int _day;  //日
  int* _a;  //指针
 
};
Date* ptr = new Date;           
 
delete ptr;  

new示意图

delete示意图

而C语言的malloc()不能调用构造free()不能调用析构。所以C语言的动态内存管理对自定义类型并不适用。

注意:对自定义类型来说,new和delete要匹配,new[]和delete[]要匹配,为什么要匹配小编后面会细讲

operator newoperator delete函数

operator newoperator delete 是系统的全局函数。

new操作符底层调用的是operator new ,delete操作符底层调用的是operator delete。

速览定义

operator new 的底层实际上还是由malloc开的空间,可以说operator new是malloc的封装。

operator delete 的底层调用的是free,operator delete 是 free的封装。

直接让new操作符调用malloc不好吗,为什么要把malloc封一层 operator new呢?

如果malloc开辟空间失败,会返回NULL。我们会手动检查接收的指针是否为空,并给自己提示错误码。而operator new 开辟空间失败会抛异常。

抛异常提示的信息会比错误码丰富,当程序运行到异常的地方时,程序会跳转到相应异常的地方处理代码。(这里只是简单的提一下抛异常的好处,让大家理解为什么要对malloc进行封装)

下面是operator new的一部分源码

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 的部分源码

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;
}

new[]和delete[]

new[]底层会调用operator new[],operator new[]底层会调用N次operator new 来为N个对象在堆上开辟空间。然后在申请的空间上调用N次构造函数。

示意图

delete[]会先调用N次析构对要释放空间进行数据和资源的清理,然后会调用operator delete[] ,operator delete[]会再调用operator delete释放该空间

示意图

开空间的细节

new[]在开空间时会多开4个字节或8个字节多开的空间用于储存new[]开空间的个数

小编用如下代码调试,带大家感受一下

class TestClass //测试类
{
public:
 
  TestClass(int a = 0)  //构造函数
    : _a(a)
  {
    cout << "TestClass():" << this << endl; //打印对象的地址
  }
 
  ~TestClass() //析构函数
  {
    cout << "~TestClass():" << this << endl; 
  }
private:
  int _a; //私有数据
};
 
void Test() //测试函数
{
 
  TestClass* ptr = new TestClass[5]; //在堆上实例化5个对象
 
  delete[] ptr; //释放空间
 
}
 
int main()
{
  Test();  
}

new5个对象

释放空间

小编解释一下具体的过程

首先new[]会先为5个对象开辟空间,并额外开4字节或8字节的空间来记录对象的个数。然后调用5次构造函数为空间中的数据初始化。

delete[]会先调用5次析构清理空间的数据,delete[]最底层的free会在额外空间的地址往后释放空间。

开额外空间记录对象的个数是为了让编译器直到要调用几次析构函数,因为我们并不会给delete[]传对象的个数

至此,我们再来探讨一下delete和new与delete[]和new[]的匹配问题

探讨匹配问题

内置类型:在堆上开空间时不涉及调用构造函数和析构函数,也不涉及复杂的内存分配。如果不匹配,出问题的几率不大(小编不鼓励大家这么写代码)。

自定义类型:一定要匹配!!!我们把两种不匹配的情况都分析一下

new和delete[]

TestClass* ptr = new TestClass; //在堆上实例化5个对象  
 
delete[] ptr; //释放空间

如果new和delete[]匹配会发生很有意思的情况

为什么会一直调用析构函数呢?因为new不会额外开空间,而delete[]会把对象的前4个或8个字节存的值当作析构函数调用的次数,但这个值是个随机值。

这是一个很严重的问题,一直被调用的析构函数不是只作用于一块空间,对象后面的空间都会被析构函数无差别的攻击。

这就像一个有着高机动性并且可以随意篡改内存的野指针。

这样写编译器不会强制报错,大家一定要注意

new[]和delete

Date* ptr = new Date[5];
delete ptr;  

首先,编译器会给出警告

代码也跑不动

这里就会发生很明显的内存泄露。deleete[]额外开的空间不会被释放。因为delete只会调用一次析构,对象空间的数据和资源不会清理干净。new[]和delete匹配会强制报错。


定位new表达式

了解即可

作用:为了能显示的调用对象的构造函数

写法

new(指向对象的指针)构造函数名

new(ptr)TestClass;

new(指向对象的指针)构造函数名type(初始化列表)

new(ptr)TestClass[1, 2, 3, 4, 5];



本篇的内容到此结束啦

相关文章
|
2月前
|
C++
【C++】深入解析C/C++内存管理:new与delete的使用及原理(二)
【C++】深入解析C/C++内存管理:new与delete的使用及原理
|
2月前
|
编译器 C++ 开发者
【C++】深入解析C/C++内存管理:new与delete的使用及原理(三)
【C++】深入解析C/C++内存管理:new与delete的使用及原理
|
19天前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
45 4
|
2月前
|
存储 程序员 编译器
简述 C、C++程序编译的内存分配情况
在C和C++程序编译过程中,内存被划分为几个区域进行分配:代码区存储常量和执行指令;全局/静态变量区存放全局变量及静态变量;栈区管理函数参数、局部变量等;堆区则用于动态分配内存,由程序员控制释放,共同支撑着程序运行时的数据存储与处理需求。
121 21
|
2月前
|
程序员 C++ 容器
在 C++中,realloc 函数返回 NULL 时,需要手动释放原来的内存吗?
在 C++ 中,当 realloc 函数返回 NULL 时,表示内存重新分配失败,但原内存块仍然有效,因此需要手动释放原来的内存,以避免内存泄漏。
|
2月前
|
存储 C语言 C++
【C++打怪之路Lv6】-- 内存管理
【C++打怪之路Lv6】-- 内存管理
41 0
【C++打怪之路Lv6】-- 内存管理
|
2月前
|
存储 C语言 C++
【C/C++内存管理】——我与C++的不解之缘(六)
【C/C++内存管理】——我与C++的不解之缘(六)
|
2月前
|
程序员 C语言 C++
C++入门5——C/C++动态内存管理(new与delete)
C++入门5——C/C++动态内存管理(new与delete)
72 1
|
2月前
|
编译器 C语言 C++
详解C/C++动态内存函数(malloc、free、calloc、realloc)
详解C/C++动态内存函数(malloc、free、calloc、realloc)
211 1
|
2月前
|
存储 安全 程序员
【C++篇】深入内存迷宫:C/C++ 高效内存管理全揭秘
【C++篇】深入内存迷宫:C/C++ 高效内存管理全揭秘
65 3