【C++杂货铺】内存管理(一)

简介: 【C++杂货铺】内存管理(一)

73c948691b594621be63079e5c662153.gif

一、C/C++内存分布

从用途和存储的角度来看,在C/C++程序中有局部数据、静态数据、全局数据、常量数据、动态申请的数据五种主要的数据,各种数据的特点如下:

局部数据:随用随创建,存储在栈区,作用域只在局部,生命周期在局部,出了作用域就销毁。

静态数据:存储在数据段,作用域只在局部或当前文件,生命周期伴随程序“一生”。

全局数据:存储在数据段,作用域在全局,生命周期伴随程序“一生”。

常量数据:存储在代码段。

动态申请的数据:存储在堆区。

fcd0b615e213435bb7a7d79b178991bd.jpg小Tips:这里需要注意char char2[] = "abcd";,"abcd"是常量字符串,存储在代码段,但是char2只是一个局部域的数组,它会把这个常量字符串拷贝一份,存储在栈区,然后char2是数组名,表示数组首元素的地址,这里表示的就是拷贝过来存储在栈区的字符串的首元素地址,并不是表示代码段中常量字符串的首元素地址。其次,const并不会改变变量的存储区域,因为pChar3是一个局部变量,所以它还是存储在栈区,前面加const修饰,是因为它存的是代码段中常量字符串的首地址。可见只要是在局部创建的变量,只要前面没有加static修饰,那这个变量一定是存储在栈区。

📖总结:

栈:又叫堆栈,用于存放非静态局部变量、函数参数、返回值等等,栈是向下生长的。

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

堆:用于程序运行时动态内存分配,堆是向上增长的。

数据段:用于存储全局数据和静态数据。

代码段:用于存储可执行的代码/只读的常量。

小Tips:数据段也可以叫做静态区,代码段也可以叫做常量区。其中数据段和代码段是站在操作系统的角度进行划分的,而静态区和常量区是站在语言的角度进行划分的。

二、C++内存管理方式

在学习C语言时,提到的4个内存管理函数malloc、realloc、calloc、free大家还记得嘛👀?忘了的话可以走传送门,回去看看。在C++中我们任然可以使用C语言中的内存管理方式,但有些地方就无能为力了,而且使用起来比较麻烦,因此C++中提出了自己的内存管理方式:通过new和delete操作符进行动态内存管理。

2.1 new和delete操作内置类型

void Test()
{
  // 动态申请一个int类型的空间
  int* ptr4 = new int;
  // 动态申请一个int类型的空间并初始化为10
  int* ptr5 = new int(10);
  // 动态申请10个int类型的空间,不初始化
  int* ptr6 = new int[10];
  // 动态申请10个int类型的空间,并初始化前三个
  int* ptr7 = new int[10]{1,2,3};
  delete ptr4;
  delete ptr5;
  delete[] ptr6;
  delete[] ptr7;
}

小Tips:new对内置类型来说,和C语言一样,只是纯粹的开空间,对于动态申请到的空间,如果不进行人为初始化的话,里面存的都是随机值。动态申请并初始化,类型后面跟的是圆括号(),动态申请多个连续空间,类型后面跟的是方括号[],如果要对这多个连续的空间初始化,可以在[]的后面跟{},里面是初始化的数据,可以只初始化一部分,后面默认补0。申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间使用new[]和delete[]

2.2 new和delete操作自定义类型

📖C语言的玩法

先来回顾一下,C语言中针对自定义类型是怎么玩的,以动态申请一个链表节点为例:

//节点类型
struct List_Node
{
  int _val;
  struct List_Node* _next;
};
//申请节点的函数
List_Node* BuyListNode(int x)
{
  List_Node* tmp = (List_Node*)malloc(sizeof(List_Node));
  if (nullptr == tmp)
  {
    perror("malloc");
    exit(-1);
  }
  tmp->_val = x;
  tmp->_next = nullptr;
}
int main()
{
  //动态申请三个节点
  List_Node* p1 = BuyListNode(1);
  List_Node* p2 = BuyListNode(2);
  List_Node* p3 = BuyListNode(3);
  return 0;
}

如上面的代码所示,在C语言中,要想为自定义类型动态申请一块空间,我们单独封装了一个函数BuyListNode,这个函数完成了空间申请、合理性检查、初始化等工作,然后通过调用这个函数实现自定义类型的动态内存申请。

📖C++的玩法

class A
{
public:
  A(int a = 0)
    : _a(a)
  {
    cout << "A():" << this << endl;
  }
  ~A()
  {
    cout << "~A():" << this << endl;
  }
private:
  int _a;
};
int main()
{
  //通过new动态申请一块空间,存储A类型变量
  A* p2 = new A(1);
  delete p2;
  //通过new动态申请3个连续空间,存储3个A类型的数据
  A* p6 = new A[3];
  delete[] p6;
  return 0;
}

8ef8af968868439988eb91913ab65a64.png

通过打印结果可以看出,new/delete对于自定义类型出了开空间,还会调用构造函数和析构函数。

小Tips:上面代码中A* p2 = new A(1);,我们进行了人为的初始化,即用圆括号传了一个1,由于new会去调用构造函数,所以这个1最终会被A类构造函数的形参接受,用于初始化对象中的_a成员变量;而A* p6 = new A[3];申请的3个连续空间(数组),我们并没有进行人为的初始化,这在当前环境下也是可以的,因为构造函数的形参有缺省值,属于默认构造函数,new去调用构造函数的时候,可以不需要传实参直接使用缺省值,但是如果A类没有默认构造函数,那我们在使用new动态申请空间的时候,一定要进行人为的传值初始化。

7a0cef59db0d44e58ffade38d040a921.png

如果A类没有默认构造函数,我们在new的时候就需要人为的传参进行初始化,像下面这样。

//通过new动态申请一块空间,存储A类型变量
A* p2 = new A(1);
//通过new动态申请10个连续空间,存储10个A类型的数据
A* p6 = new A[3]{ 1, 2, 3 };//通过隐式类型转换去初始化
A* p6 = new A[3]{ A(1), A(2), A(3) };//通过匿名对象去初始化
//A* p6 = new A[3]{ A(1), A(2)};//申请了三块空间,只初始化了两个,这是不被允许的

小Tips:对于动态申请的自定义类型数组,如果该自定义类型没有默认构造,则需要进行人为的初始化,并且申请了几块空间就要初始化几个,不像动态申请的内置类型数组,可以只初始化前面一部分,后面默认是0。此外,如果该类的构造函数有多个形参(超过一个),就不支持隐式类型转换去初始化了,可以继续使用匿名对象去初始化。

📖一些建议

这里建议大家malooc、free、new、delete、new[]、delete[]不要混用,严格按照下面的对应关系去使用:

malloc——free

new——delete

new[]——delete[]

对于内置类型,混用了可能没什么,但是对于自定义类型可就不一定了。

d9c47d757fd3438c89899bda1f903712.png


三、operator new与operator delete函数

new和delete是用户进行动态内存申请和释放的操作符,operator new和operator delete是系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过调用operator delete全局函数来释放空间。

小Tips:operator new和operator delete并不是简单的运算符重载哦。

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和operator delete的实现,从中我们可以发现,operator new实际上也是通过malloc来申请空间,如果malloc申请空间成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施就继续申请,否则就抛异常。operator delete最终是通过free来释放空间的。

小Tips:operator new、operator delete和malloc、free在功能上相似,都是到堆上申请空间,然后释放空间,它们的区别在于,前者失败了会抛异常,而后者失败了是返回错误码,通过返回值来表示这一块有问题。抛异常是面向对象语言的一大特色,关于异常春人会在后面的文章中给大家分享,感兴趣的友友可以关注一下春人哟。


目录
相关文章
|
22天前
|
存储 缓存 C语言
【c++】动态内存管理
本文介绍了C++中动态内存管理的新方式——`new`和`delete`操作符,详细探讨了它们的使用方法及与C语言中`malloc`/`free`的区别。文章首先回顾了C语言中的动态内存管理,接着通过代码实例展示了`new`和`delete`的基本用法,包括对内置类型和自定义类型的动态内存分配与释放。此外,文章还深入解析了`operator new`和`operator delete`的底层实现,以及定位new表达式的应用,最后总结了`malloc`/`free`与`new`/`delete`的主要差异。
42 3
|
29天前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
68 4
|
2月前
|
存储 程序员 编译器
简述 C、C++程序编译的内存分配情况
在C和C++程序编译过程中,内存被划分为几个区域进行分配:代码区存储常量和执行指令;全局/静态变量区存放全局变量及静态变量;栈区管理函数参数、局部变量等;堆区则用于动态分配内存,由程序员控制释放,共同支撑着程序运行时的数据存储与处理需求。
138 21
|
2月前
|
程序员 C++ 容器
在 C++中,realloc 函数返回 NULL 时,需要手动释放原来的内存吗?
在 C++ 中,当 realloc 函数返回 NULL 时,表示内存重新分配失败,但原内存块仍然有效,因此需要手动释放原来的内存,以避免内存泄漏。
|
2月前
|
存储 C语言 C++
【C++打怪之路Lv6】-- 内存管理
【C++打怪之路Lv6】-- 内存管理
44 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/C++内存管理(下)
50 0
|
2月前
|
存储 Linux C语言
C/C++内存管理(上)
C/C++内存管理(上)
39 0
|
2月前
|
Linux C++
Linux c/c++文件虚拟内存映射
这篇文章介绍了在Linux环境下,如何使用虚拟内存映射技术来提高文件读写的速度,并通过C/C++代码示例展示了文件映射的整个流程。
55 0