学习C++就看这篇--->内存管理之new和delete
见下图:想自测的小伙伴可以看下述的题:代码语言:javascriptAI代码解释A.栈 B.堆 C.数据段(静态区) D.代码段(常量区)globalVar在哪里?____ staticGlobalVar在哪里?____ staticVar在哪里?____localVar在哪里?____ num1 在哪里?____ char2在哪里?____pChar3在哪里?____ ptr1在哪里?____
见下图:

想自测的小伙伴可以看下述的题:
代码语言:javascript
AI代码解释
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);
}
A.栈 B.堆 C.数据段(静态区) D.代码段(常量区)
globalVar在哪里?____ staticGlobalVar在哪里?____ staticVar在哪里?____
localVar在哪里?____ num1 在哪里?____ char2在哪里?____
pChar3在哪里?____ ptr1在哪里?____ *char2在哪里?___
*pChar3在哪里?____ *ptr1在哪里?____
答案如下:
代码语言:javascript
AI代码解释
int globalVar = 1;//全局变量定义在数据段
static int staticGlobalVar = 1;//静态全局变量也定义在数据段
//上面这两个全局变量是在main函数之前就已经初始化,在哪都能用,作用域是全局的
//但是这两个是有区别的,区别在于链接属性是不一样的,globalVar是所有文件可见,staticGlobalVar只在当前文件可见
void Test()
{
static int staticVar = 1;//静态局部变量也定义在数据段,这一个是运行到这里再初始化,它的作用域在Test函数中,只能在Test函数中使用
int localVar = 1;//局部变量定义在栈区
int num1[10] = { 1, 2, 3, 4 };//数组定义在栈区
char char2[] = "abcd";//数组定义在栈区
const char* pChar3 = "abcd";//指针定义在栈区
int* ptr1 = (int*)malloc(sizeof(int) * 4);//ptr1是指针存放在栈区,但是它所指向的内容却是存放在堆区
int* ptr2 = (int*)calloc(4, sizeof(int));//ptr2是指针存放在栈区,但是它所指向的内容却是存放在堆区
int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4);//ptr3是指针存放在栈区,但是它所指向的内容却是存放在堆区
free(ptr1);
free(ptr3);
}
需要注意:上面讲述了全局的全局变量和静态变量的区别,以及局部的静态变量与全局变量的区别。
趁势我们把这些内容也回顾下:
sizeof(num1) = ____; sizeof(char2) = ____; sizeof(pChar3) = ____;
sizeof(ptr1) = ____; strlen(char2) = ____; strlen(pChar3) = ____;
答案如下:
sizeof(num1) = 40; sizeof(char2) = 5; sizeof(pChar3) = 4/8;
sizeof(ptr1) = 4/8; strlen(char2) = 4; strlen(pChar3) = 4;
sizeof 和 strlen 区别?
本质不同
sizeof:- 是编译时运算符(不是函数),在编译期间确定结果。
- 功能:计算变量或数据类型所占内存的字节数(包括字符串末尾的
\0)。
strlen:- 是运行时函数(定义在
<string.h>中)。 - 功能:计算字符串的实际长度(从起始地址到第一个
\0前的字符数,不包括\0)。
- 是运行时函数(定义在
代码语言:javascript
AI代码解释
//静态字符数组
char str[] = "Hello";
//sizeof(str) → 结果为 6(5个字符 + 结尾的 \0)。
//strlen(str) → 结果为 5(只计算有效字符 'H','e','l','l','o')。
//指针指向的字符串
const char* ptr = "Hello";
//sizeof(ptr) → 结果为 指针大小(4 或 8 字节,取决于系统)。
//strlen(ptr) → 结果为 5(字符串内容长度)。
//固定长度数组
char arr[100] = "abc";
//sizeof(arr) → 结果为 100(整个数组的内存大小)。
//strlen(arr) → 结果为 3(字符串 "abc" 的长度)。
总结如下:
|
特性 |
sizeof |
strlen |
|---|---|---|
|
类型 |
运算符(编译时求值) |
函数(运行时求值) |
|
参数 |
可接受变量、数据类型或表达式 |
只接受字符串地址(const char*) |
|
计算内容 |
内存占用大小(包括 \0) |
字符串有效长度(不包括 \0) |
|
安全性 |
无副作用(不访问内存) |
需遍历字符串直到 \0(可能越界) |
|
数组退化 |
对数组返回实际大小 |
数组退化为指针后失效 |
|
指针处理 |
返回指针本身的大小(4/8 字节) |
返回指针指向字符串的长度 |
📕二、C++内存管理方式
✨2.1 C语言内存管理方式(回顾)
C语言当中我们知道malloc/calloc/realloc/free,在此进行回顾下:
|
特性 |
malloc |
calloc |
realloc |
|---|---|---|---|
|
初始化 |
不初始化(垃圾值) |
初始化为零 |
保留原数据,新增部分不初始化 |
|
参数 |
1 个(总字节数) |
2 个(元素数量 × 元素大小) |
2 个(原指针 + 新字节数) |
|
主要用途 |
通用内存分配 |
需要初始化的数组 |
调整已分配内存的大小 |
|
内存计算 |
直接指定字节数 |
自动计算 num * size |
修改现有内存块大小 |
|
性能 |
较快(不初始化) |
较慢(需初始化) |
可能涉及内存复制(较慢) |
malloc (Memory Allocation)
- 功能:分配指定字节数的未初始化内存
- 特点:
- 只接受一个参数:需要分配的字节数(
size) - 分配的内存内容是未初始化的(包含随机垃圾值)
- 适用于需要精确控制内存大小的场景
- 只接受一个参数:需要分配的字节数(
代码语言:javascript
AI代码解释
void* malloc(size_t size);
int* arr = (int*)malloc(5 * sizeof(int)); // 分配 20 字节(假设 int 占 4 字节)
calloc (Contiguous Allocation)
- 功能:分配指定数量和大小的内存块并初始化为零
- 特点:
- 接受两个参数:元素数量(
num)和单个元素大小(size) - 分配的内存内容会被自动初始化为 0(比
malloc更安全) - 总分配大小 =
num * size - 适用于需要初始化数组的场景
- 接受两个参数:元素数量(
代码语言:javascript
AI代码解释
void* calloc(size_t num, size_t size);
int* arr = (int*)calloc(5, sizeof(int)); // 分配并初始化 5 个 int(全 0)
realloc (Re-allocation)
- 功能:调整已分配内存块的大小
- 特点:
- 接受两个参数:原内存指针(
ptr)和新字节大小(new_size) - 可能的行为:
- 原地扩展/缩小内存(如果后续空间足够)
- 或重新分配新内存,复制旧数据,释放旧内存
- 不会初始化新增的内存区域
- 如果
ptr为NULL,则等价于malloc(new_size)
- 接受两个参数:原内存指针(
代码语言:javascript
AI代码解释
void* realloc(void* ptr, size_t new_size);
int* new_arr = (int*)realloc(arr, 10 * sizeof(int)); // 扩展到 40 字节
✨2.2 new/delete
C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力,而且使用起来比较麻烦,因 此C++又提出了自己的内存管理方式:通过new和delete操作符进行动态内存管理。
用法如下:
代码语言:javascript
AI代码解释
class A
{
public:
A(int n = 0)
{
cout << "A()" << endl;
}
~A()
{
cout << "~A()" << endl;
}
};
int main()
{
A* p1 = (A*)malloc(sizeof(A));
free(p1);
A* p2 = new A;//对于自定义类型是如此
delete p2;
int* p3 = new int;//对于内置类型这样定义
int* p4 = new int(100);//这是定义个int类型并初始化的形式
int* p5 = new int[100];//这是定义数组的形式
delete[100] p5;//对于数组释放空间是这样定义
int* p6 = new int[100]{ 1,2,3 };//这是定义数组的形式并初始化的形式
return 0;
}
注意:申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用 new[]和delete[]
相信会有这样的疑惑,既然C++继承了C语言的用法,即默认malloc、calloc等函数是可以使用的,为什么还要new/delete这些呢?
这就需要讲述它们的差别:
new与malloc
- 对于内置类型来说,new和malloc没有什么区别
- 对于自定义类型来说,new会调用自定义类型的构造函数,而malloc不会
- new可以通过构造函数初始化变量的内容
delete与free:
- 对于内置类型,delete和free没有什么区别
- 对于自定义类型,delete会调用自定义类型的析构函数,而free不会
- delete面对不同的情况需要加上[ ],然而free不用考虑
✨2.3 operator new与operator delete函数
new和delete是用户进行动态内存申请和释放的操作符
operator new 和operator delete是系统提供的全局函数
new在底层调用operator new全局函数来申请空间,delete在底层通过 operator delete全局函数来释放空间
operator new使用方式:
代码语言:javascript
AI代码解释
void* p1 = malloc(1024);
void* p2 = operator new (1024);
可以观察下面operator new库中的实现本质就是用malloc申请空间
代码语言:javascript
AI代码解释
void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{
void *p;
while ((p = malloc(size)) == 0)
if (_callnewh(size) == 0)
{
// 如果申请内存失败了,这里会抛出bad_alloc 类型异常
static const std::bad_alloc nomem;
_RAISE(nomem);
}
return (p);
}
所以 operator new域malloc正常使用的方式都是一样的,只不过处理错误的方式不一样
代码语言:javascript
AI代码解释
int main()
{
size_t size = 2;
void* p1 = malloc(size * 1024 * 1024 * 1024);
cout << p1 << endl; //申请失败返回0
try
{
void* p2 = operator new (size * 1024 * 1024 * 1024);
cout << p2 << endl;//失败抛异常(面向对象处理错误的方式)
}
catch (exception& e)
{
cout << e.what() << endl;
}
return 0;
}
抛异常不是本章节的重点,等到我们学习到此的时候,再着重学习!
所以malloc、operator new、new它们的关系如下:
malloc operator new -->malloc+失败抛异常 new --> operator new + 构造函数 new比malloc不一样的地方:1.调用构造函数初始化 2. 失败抛异常 delete和free不一样的地方在于delete调用析构函数清理 operator delete 和free没有本质区别
补充知识: 相信上述代码你关注到了 size_t size = 2; size * 1024 * 1024 * 1024; 为什么不写成2*1024*1024*1024?注意此处我们是为了让它开辟不出空间,如果写成2*1024*1024*1024那这里就会报溢出错误,原因如下:
- 整数类型范围
- 在 C ++ 中,整数类型有不同的长度和表示范围。
int通常是一个有符号类型,在 32 - bit 系统中占 32 位。它的取值范围是 - 2^(31) 到 2^(31) - 1,即 - 2147483648 到 2147483647。当计算结果超过这个范围时,就会发生溢出。 size_t是一个无符号整数类型,用于表示大小和索引等非负值。在 32 - bit 系统中,size_t通常是 32 位无符号整数,其范围是 0 到 4294967295。无符号整数在计算过程中不会出现负数,当计算结果超过其最大值时,会产生溢出,但不会像有符号整数那样出现负值,而是会循环回到 0 并继续计算。
- 在 C ++ 中,整数类型有不同的长度和表示范围。
更多推荐
所有评论(0)