【C++】C/C++内存管理(一)

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

在之前C语言的学习中,我们学过了C是如何进行动态内存管理的,也简单的了解过C/C++程序的内存开辟。

这篇文章呢,我们重点来学习一下C++的内存管理方式。

1. C/C++内存分布

C/C++的内存分布我们在之前也是了解过的,那我们这里再简单复习一下。

我们先来看下面的一段代码和一些相关的问题:

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

问:

f012d89414ea4efc9e2a23408595d51e.png

我们来分析一下:


首先globalVar是一个全局变量,staticGlobalVar是一个静态全局变量,staticVar是静态局部变量,都在静态区(数据段)。

然后localVar是个局部变量,num1是个整型数组,那它们是在栈上的。


接着看


char2应该在哪?🆗,这里涉及到我们之前C语言讲过的一个关于常量字符串的知识,我们先来复习一下:

402b8373b33a49de8af523310e1236ed.png

相信现在大家就知道了,char2这个字符数组还是在栈上的,只是拿代码段(常量区)的一个常量字符串去初始化它了,然后*char2,char2是数组首元素地址,那*char2就是数组首元素,还是在栈上。

再看pChar3是一个局部指针变量,在栈上,但是pChar3指向常量区的一个常量字符串,所以*pChar3是在常量区。

然后ptr1还是局部指针变量,在栈上,ptr1指向的空间是malloc出来的,在堆上。

所以,答案是这样的:

9c2fcc6eede74f3f999299f92f308410.png

再看几个填空题:4ef9f436f8e74cf49740a2886d39e85e.png

sizeof(num1),数组名放到sizeof里面代表整个数组,num1是10个元素的整型数组,所以答案是40;sizeof(char2),char2里面有5个字符(字符串隐藏结束标志\0),所以是5;strlen(char2)求字符串长度,是4 ;sizeof(pChar3),指针变量,大家为4或8字节;strlen(pChar3),同样求长度是4;sizeof(ptr1),指针变量,4或8 字节。


最后,再来复习一下C/C++的内存区域划分:

7921b3b210734ed7a9f08af63ebc4c11.png

dc683302344844b183e943e4d236975d.png

2. C的动态内存管理方式

那我们再来简单复习一下C语言的内存管理方式:

void Test()
{
  int* p1 = (int*)malloc(sizeof(int));
  free(p1);
  // 1.malloc/calloc/realloc的区别是什么?
  int* p2 = (int*)calloc(4, sizeof(int));
  int* p3 = (int*)realloc(p2, sizeof(int) * 10);
  // 这里需要free(p2)吗?
  free(p3);
}

大家回忆一下,malloc和calloc的区别是什么?

它们都可以在堆上开辟内存空间,除了传参不一样之外,两者的区别在于calloc会把开辟出来的空间全部初始化为0,而malloc不会去初始化。

然后realloc是用来扩容的,有两种方式,原地扩和异地扩。

这里就不细说了,如果大家遗忘了,可以看一下之前的文章链接: link

3. C++内存管理方式

C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力,而且使用起来比较麻烦,因此C++又提出了自己的内存管理方式:

通过new和delete操作符进行动态内存管理。

3.1 new/delete操作内置类型

在C语言中:

我们使用malloc/calloc去申请空间,是不是需要自己计算需要开辟空间的大小,然后传参,返回值呢是void*,还需要我们自己强转。

int* pp1 = (int*)malloc(sizeof(int));

那在C++中,我们就可以这样:

int* p1 = new int;

直接用一个操作符叫做new,我们也不需要sizeof计算大小,直接给类型就行了,而且也不需要强制类型转换。

如果要动态申请10个整型大小的空间:

d5c8cb60024f45c6b21921ff43cde4ee.png

直接这样就可以了。🆗,那大家思考一下:


C++搞出来这样新的动态内存管理的方式,仅仅是为了用起来比C语言方便,简洁一点吗?

那他会不会进行一些优化,比如,C++里这样搞会对空间进行初始化?

cecef1dbda494edf95fe67c3abb39a67.png

好的,并没有初始化。

那这样看的话,C++搞出new这些东西和C语言的malloc这些对于内置类型的操作好像除了用法之外也没有什么很大的区别。

那所以呢?

C++搞出这些东西更多的是为了自定义类型,那new和delete操作自定义类型我们后面也会专门讲解,先不急。


那另外:

我们malloc的时候由于可能会开辟失败的缘故,所以我们一般malloc之后会进行一个检查,如果返回的是空指针,就代表开辟失败。

那我们的new有没有可能失败呢?

当然也是有可能的,但是new失败不是返回空指针,而是抛异常,那关于异常我们后面也会讲到。

然后呢:

我们看到上面对于内置类型new出来的空间并没有被初始化,但是C++其实有方法去对new出来的空间进行初始化

怎么做呢?98cad62b2e5f40038aa50bed3127c136.png

直接在后面加圆括号然后放上我们要初始化的值就行了

98cad62b2e5f40038aa50bed3127c136.png

要注意与这样写的区别:4420fc5330fa49c5901bfa85f24c9554.png


那对于我们使用new动态开辟的数组,我们可以初始化吗?

也是可以的:832e20ee126c41dabeb3129a74c95717.png


直接在后面跟大括号进行初始化。

407f1d6c63894eb596be4e615157d8a6.png

那除了new这个操作符之外呢,我们再来学一个操作符叫做delete:


我们C语言阶段使用malloc/calloc在堆上开辟出来的空间使用完是不是要使用free释放啊。

那如果是我们new出来的空间呢,我们说new和delete操作符也是进行动态内存管理的,所以new出来的空间也是堆上的,那new出来的空间使用完我们要怎么释放呢?

🆗,用一个操作符叫做delete:

9e872b824ad943e7a03f7ce9ec913f0f.png

不用加括号,因为我们今天学的new和delete是操作符,而malloc/calloc是库里面的函数。

那使用new动态开辟的数组怎么销毁呢?

54e6ffad422745438a41645d58cac8a4.png

注意delete后面加一个方括号。

5bf6c20a3e3943d1aea025b6ee8a1422.png

那大家可能会想:


既然都是申请空间和释放空间,那可以不可以混起来用呢?

就是malloc的空间可不可以不 free ,而使用delete ,new 出来的空间去free,或者是其它方式的混用。

🆗,那想告诉大家的是:

申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用new[]和delete[]

注意:匹配起来使用。

我们不要去不匹配的用,不匹配的话,有些情况可能没事,但是有些情况下可能就出错了。

至于原因我们后面也会浅浅的给大家解释一下。


3.2 new和delete操作自定义类型

那我们上面提到:C++搞出new和delete这些东西更多的是为了自定义类型,那接下来我们就来看一下new和delete操作自定义类型有什么特别的地方。


那现在有这样一个自定义类型类A:

class A
{
public:
  A(int a = 0)
    : _a(a)
  {
    cout << "A():" << this << endl;
  }
  ~A()
  {
    cout << "~A():" << this << endl;
  }
private:
  int _a;
};

那大家看:

80464f3eee1144efaa3eae17768452d2.png

这两种写法有什么区别吗?

1a12dd8663904ebe9b28ab0a7f18273e.png

我们看到用malloc呢就只是开辟了空间。

但是用new呢?

1c72eae0714f4f989d5c225ba18c776a.png

除了开辟空间还自动调用了构造函数进行初始化。

9f49f2ab3e444ed5bb05db1a1131fd7e.png

free只是释放了空间;

delete除了释放指针指向的空间还会调用析构函数对自定义类型进行析构。

551afbfa0824469dbbe403e0889310a5.png

当然:

如果对应的构造函数有参数,我们new的同时也可以传参:

cdff025fd60243059f3daa9bb837084d.png

所以:

在申请和释放自定义类型的空间时,new会自动调用构造函数,delete会自动调用析构函数,而malloc与free不会。

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