【C++】C/C++ 内存管理从入门到进阶
代码语言:javascriptAI代码解释globalVar在哪里?C staticGlobalVar在哪里?__C__ staticVar在哪里?__C__ localVar在哪里?__A__ num1 在哪里?__A__char2在哪里?__A__ *char2在哪里?__A__pChar3在哪里?__A__ *pChar3在哪里?__D__ptr1在哪里?__A__ *ptr1在哪里?__B
【相关题目】
代码语言: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);
}
1. 选择题:
选项: A.栈 B.堆 C.数据段(静态区) D.代码段(常量区)
globalVar在哪里? C staticGlobalVar在哪里?__C__ staticVar在哪里?__C__ localVar在哪里?__A__ num1 在哪里?__A__ 【分析】:
- globalVar全局变量在数据段
- staticGlobalVar静态全局变量在静态区
- staticVar静态局部变量在静态区
- localVar局部变量在栈区
- num1局部变量在栈区
char2在哪里?__A__ *char2在哪里?__A__
pChar3在哪里?__A__ *pChar3在哪里?__D__
ptr1在哪里?__A__ *ptr1在哪里?__B__
【分析】:
- char2局部变量在栈区
- char2是一个数组,把后面常量串拷贝过来到数组中,数组在栈上,所以*char2在栈上
- pChar3局部变量在栈区
- *pChar3得到的是字符串常量字符在代码段
- ptr1局部变量在栈区
- *ptr1得到的是动态申请空间的数据在堆区
2. 填空题:
sizeof(num1) = __40__; //数组大小,10个整形数据一共40字节 sizeof(char2) = __5__; //包括\0的空间 strlen(char2) = __4__; //不包括\0的长度 sizeof(pChar3) = __4__; //pChar3为指针 strlen(pChar3) = __4__; //字符串“abcd”的长度,不包括\0的长度 sizeof(ptr1) = __4__; //ptr1是指针
二、栈与堆对比
栈和堆是日常开发中最常接触的内存区域,二者的差异直接决定了使用场景的选择,以下是全方位对比:
|
特性 |
栈区(Stack) |
堆区(Heap) |
|---|---|---|
|
管理方式 |
编译器自动分配释放 |
程序员手动通过new/delete等接口管理 |
|
空间大小 |
较小(几 MB) |
较大(受系统内存限制) |
|
访问速度 |
极快(栈指针操作) |
较慢(需内存寻址) |
|
生命周期 |
随作用域自动销毁 |
显式释放前持续存在 |
|
内存碎片 |
无 |
频繁分配释放易产生碎片 |
|
增长方向 |
从高地址到低地址 |
从低地址到高地址 |
|
典型场景 |
局部变量、函数参数 |
大对象、跨作用域数据 |
|
风险点 |
栈溢出(过大局部变量) |
内存泄漏(未释放)、野指针 |
三、动态内存管理
C 和 C++ 都支持堆内存的动态管理,但提供了不同的接口,使用时需注意语法规范和匹配原则,避免混用导致内存问题。
1. C语言:malloc/calloc/realloc与free
malloc(size_t size):分配指定字节数的堆内存,返回 void * 指针,需手动类型转换。calloc(size_t num, size_t size):分配 num 个大小为 size 的连续内存,初始化为 0。realloc(void* ptr, size_t size):调整已分配内存的大小,可能会移动内存块。free(void* ptr):释放通过上述函数分配的堆内存,ptr 必须指向堆内存起始地址。
【示例代码】:
代码语言:javascript
AI代码解释
#include <stdio.h>
#include <stdlib.h>
int main()
{
// 1. malloc:分配内存(不初始化)
int* m = (int*)malloc(2 * sizeof(int));
m[0] = 1; m[1] = 2;
printf("malloc: %d, %d\n", m[0], m[1]);
// 2. calloc:分配并初始化为0
int* c = (int*)calloc(2, sizeof(int));
printf("calloc: %d, %d\n", c[0], c[1]);
// 3. realloc:扩容malloc的内存
m = (int*)realloc(m, 3 * sizeof(int));
m[2] = 3;
printf("realloc后: %d, %d, %d\n", m[0], m[1], m[2]);
// 4. free:释放内存
free(m);
free(c);
return 0;
}
2. C++:new/delete与new[]/delete[]
C++ 在 C 的基础上提供了更安全的动态内存接口,支持对象的构造和析构:
new T**:**分配单个 T 类型对象的内存,自动调用构造函数初始化。new T[n]**:**分配 n 个 T 类型对象的数组内存,自动调用每个对象的构造函数。delete ptr**:**自动调用析构函数,释放单个对象内存。delete[] ptr**:**自动调用每个对象的析构函数,释放数组内存。
【注意】:
- 接口必须成对使用,
new对应delete,new[]对应delete[],混用会导致内存泄漏或程序崩溃。 - 无需手动类型转换,
new返回对应类型指针,且分配失败时抛出bad_alloc异常(而非返回 NULL)。
【示例代码】:
代码语言:javascript
AI代码解释
int main()
{
//内置类型
int* a2 = new int;//不初始化
delete a2;
int* a3 = new int(1);//初始化
delete a3;
int* a4 = new int[4];//数组~不初始化
delete[] a4;
int* a5 = new int[4] {1, 2, 3, 4};//数组~初始化,不完全初始化的话,未初始化的都会被初始化为0
delete[] a5;
//自定义类型~用法和内置类型同理
// new/delete 和 malloc/free最大区别是:
// new/delete对于【自定义类型】除了开空间还会调用构造函数和析构函数
return 0;
}
【面试题】:malloc/calloc/realloc的区别?
malloc 、 calloc 、 realloc 的区别主要在三点:
- 参数不同: malloc 是单参数(总字节数), calloc 是双参数(元素个数+单个大小), realloc 是双参数(原地址+新字节数)。
- 初始化不同: malloc 分配的内存未初始化, calloc 会自动初始化为0, realloc 的新增部分未初始化。
- 功能不同: malloc 和 calloc 是首次分配内存, realloc 是调整已分配内存的大小。
【问题】:这里需要free(p2)吗?
代码语言:javascript
AI代码解释
void Test ()
{
int* p2 = (int*)calloc(4, sizeof (int));
int* p3 = (int*)realloc(p2, sizeof(int)*10);
free(p3 );
}
不需要free(p2),无论是异地扩容还是原地扩容,最终都不需要专门释放p2,如果是原地扩容,p1和p2指向同一块空间,如果是异地的话,将原内容拷贝后会自动释放原来p2指向的空间

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

但是,追踪到底还是会调用malloc和free,因为毕竟c++兼容C语言,有现成开空间的函数直接调用不就行了呗
下面是operator new函数源码,malloc失败返回空,new失败抛异常(后面会学到)

【new底层汇编】

从图中可以看到是先调用operator new,再调用相对应的构造函数
【delete底层汇编】

delete的底层是先调用析构函数,然后再调用operator delete
【模拟栈对象如何在堆上申请/释放空间】

先为栈对象开空间,然后调用该对象的构造函数为_array开辟空间,最终让栈上的p1指针指向堆上的对象(栈对象和栈内存区域是两个不同领域的概念)
更多推荐



所有评论(0)