ok,类和对象终于结束了,实在不容易,事实证明:世上无难事,只怕有心人。只要好好学,死学,硬学,酷酷学,就没有学不会的。
本博客章节是对过去一些知识点的总结,会用到之前学的函数栈帧,堆栈空间调用。
欧克,那废话不多说,直接开始今天的学习。
C/C++内存管理
我们先回顾下图:这是C/C++内存区域划分

下面来看一下练习题------分析下面程序并回答问题
cpp
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在哪里?____
*char2在哪里?___
pChar3在哪里?____
*pChar3在哪里?____
ptr1在哪里?____
*ptr1在哪里?___
答案(附带分析):
staticVar在哪里?__C_ localVar在哪里?__A_
globalVar在哪里?__C_ staticGlobalVar在哪里?__C_
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得到的是动态申请空间的数据在堆区
sizeof(num1) = _40__;数组大小,10个整形数据一共40字节
sizeof(char2) = __5_; 包括\0的空间 strlen(char2) = __4_;不包括\0的长度
sizeof(pChar3) = _4/8__; 分32位和64位 pChar3为指针 strlen(pChar3) = __4_;字符串"abcd"的长度,不包括\0的长度
sizeof(ptr1) = _4/8__;分32位和64位 ptr1是指针
1.栈又叫堆栈--非静态局部变量/函数参数/返回值等等,栈是向下增长的。
- 内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口
创建共享共享内存,做进程间通信。(目前现在只需要了解一下,后续会讲)
-
堆用于程序运行时动态内存分配,堆是可以上增长的。
-
数据段--存储全局数据和静态数据。
-
代码段--可执行的代码/只读常量。
C语言中动态内存管理方式:(malloc/calloc/realloc/free)
我们在C语言中已用过内存函数向堆中申请空间,那这三个内存函数的区别是什么,还记得吗?
malloc(memory allocation)
c
void* malloc(size_t size);
从堆中分配一块指定字节大小的连续内存空间。
成功:返回指向分配内存起始地址的 void* 指针(需强制类型转换后使用)。
失败:返回 NULL(如内存不足时)。
num:要分配的元素个数。
size:每个元素的字节大小。
cpp
// 分配 10 个 int 类型的内存(共 40 字节,假设 int 占 4 字节)
int* arr = (int*)malloc(10 * sizeof(int));
if (arr == NULL) { // 必须检查是否分配成功
perror("malloc failed");
exit(1);
}
// 此时 arr 中的值是随机的,需手动初始化
arr[0] = 10;
free(arr); // 释放内存
arr = NULL; // 避免野指针
calloc(contiguous allocation)
c
void* calloc(size_t num, size_t size);
从堆中分配一块连续内存,内存大小为 num * size(即元素个数 × 每个元素的字节大小)。将分配的内存全部初始化为 0(这是与 malloc 的核心区别)。返回值同malloc
num:要分配的元素个数。
size:每个元素的字节大小。
cpp
// 分配 10 个 int 类型的内存(共 40 字节),并初始化为 0
int* arr = (int*)calloc(10, sizeof(int));
if (arr == NULL) {
perror("calloc failed");
exit(1);
}
// 此时 arr[0] ~ arr[9] 全部为 0
free(arr);
arr = NULL;
realloc(re-allocation)
c
void* realloc(void* ptr, size_t new_size);
调整已分配内存的大小(扩大或缩小),是动态内存管理的 "灵活工具"。
ptr:指向已通过 malloc/calloc/realloc 分配的内存地址(若为 NULL,则 realloc 等价于 malloc(new_size))。
new_size:调整后的内存字节大小(可大于 / 小于原大小)。
原地扩容:如果原内存块后面有足够的连续空间,直接扩展内存,返回原指针。
异地扩容:如果原内存块后面没有足够空间,会在堆中重新分配一块 new_size 大小的内存,将原内存中的数据拷贝到新内存,自动释放原内存,返回新指针。
缩容:直接截断原内存,返回原指针(截断的部分会被释放,但数据丢失)。
成功:返回调整后内存的起始地址(可能是原指针,也可能是新指针)。
失败:返回 NULL(此时原内存块不会被释放,需注意)。
cpp
// 先分配 10 个 int 的内存
int* arr = (int*)malloc(10 * sizeof(int));
if (arr == NULL) {
perror("malloc failed");
exit(1);
}
// 扩容为 20 个 int 的内存
int* new_arr = (int*)realloc(arr, 20 * sizeof(int));
if (new_arr == NULL) { // 扩容失败,原 arr 仍有效
perror("realloc failed");
free(arr); // 释放原内存
exit(1);
}
arr = new_arr; // 指向新内存(或原内存)
// 缩容为 5 个 int 的内存
new_arr = (int*)realloc(arr, 5 * sizeof(int));
if (new_arr == NULL) {
perror("realloc failed");
free(arr);
exit(1);
}
arr = new_arr;
free(arr);
arr = NULL;
C++内存管理方式
C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力,而且使用起来比较麻烦,因此C++又提出了自己的内存管理方式:通过new和delete操作符进行动态内存管理。
new/delete操作内置类型
代码案例:
cpp
void Test()
{
// 动态申请一个int类型的空间
int* ptr4 = new int;
// 动态申请一个int类型的空间并初始化为10
int* ptr5 = new int(10);
// 动态申请10个int类型的空间
int* ptr6 = new int[3];
delete ptr4;
delete ptr5;
delete[] ptr6;
}
注意:申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用
new[]和delete[],注意:匹配起来使用。
new和delete操作自定义类型
new/delete 和 malloc/free最大区别是 new/delete对于【自定义类型】除了开空间还会调用构造函数和析构函数
代码案例:
cpp
class A
{
public:
A(int a = 0)
: _a(a)
{
cout << "A():" << this << endl;
}
~A()
{
cout << "~A():" << this << endl;
}
private:
int _a;
};
int main()
{
A* p1 = (A*)malloc(sizeof(A));
A* p2 = new A(1);
free(p1);
delete p2;
// 使用内置类型是几乎是一样的
int* p3 = (int*)malloc(sizeof(int)); // C
int* p4 = new int;
free(p3);
delete p4;
A* p5 = (A*)malloc(sizeof(A)*10);
A* p6 = new A[10];
free(p5);
delete[] p6;
return 0;
}
注意:在申请自定义类型的空间时,new会调用构造函数,delete会调用析构函数,而malloc与free不会。
为了验证结果我们在vs编译器下观看到底有没有调用构造
先进入new的底层

进来之后发现是运算符重载,然后在反汇编下看第二行确实用运算符重载调用了,其实底层就是malloc。在下图中蓝色行中可以看到调用了构造,delete也是这样的会先调析构后free。这里就不一 一看了。

operator new与operator delete函数
内置类型
new和delete是用户进行动态内存申请和释放的操作符,operator new 和operator delete是系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间
自定义类型
new的原理
- 调用operator new函数申请空间
- 在申请的空间上执行构造函数,完成对象的构造
delete的原理
- 在空间上执行析构函数,完成对象中资源的清理工作
- 调用operator delete函数释放对象的空间
new T[N]的原理
- 调用operator new[]函数,在operator new[]中实际调用operator new函数完成N个对象空间的申请
- 在申请的空间上执行N次构造函数
delete[]的原理
- 在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理
- 调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释放空间
如果new申请空间失败了如何处理
在C语言中我们申请空间失败就会打印一下错误信息,并退出重新来。
c
#include <stdio.h>
#include <stdlib.h> // malloc、free、exit
#include <errno.h> // errno
#include <string.h> // strerror
int main() {
//注意这是一个案例,内置类型 如:int也是可以
// 尝试分配一个极大的内存(必然失败,比如 SIZE_MAX 是无符号最大值)
size_t huge_size = (size_t)-1; // 等价于 SIZE_MAX(定义在 stdlib.h)
int* ptr = (int*)malloc(huge_size);
// 核心:检查返回值是否为 NULL
if (ptr == NULL) {
// 方式1:用 perror 打印错误(推荐,简单)
perror("malloc failed");
// 方式2:用 strerror 打印错误(更灵活,可自定义输出格式)
fprintf(stderr, "malloc failed: %s (errno: %d)\n", strerror(errno), errno);
// 失败后:终止程序(exit(1) 表示异常退出,0 表示正常退出)
exit(EXIT_FAILURE); // EXIT_FAILURE 等价于 1,定义在 stdlib.h
}
// 分配成功后的逻辑(这里不会执行)
printf("内存分配成功,地址:%p\n", (void*)ptr);
free(ptr); // 释放内存
ptr = NULL; // 避免野指针
return 0;
}
在C++中我们申请空间一般情况下不会失败,只要别太过分,程序不会崩溃,如果申请空间失败了那就要抛异常了。这里大概描述一下,以后会详细讲解:抛异常。
C++ 标准规定,默认的 new 表达式(如 int* p = new int;)在内存分配失败时,会抛出 std::bad_alloc 异常(该异常继承自 std::exception,定义在 头文件),而不会返回 NULL。
因此,我们需要用 try-catch 块捕获这个异常,进行错误处理。
cpp
#include <iostream>
#include <new> // 必须包含:std::bad_alloc 定义
#include <exception> // std::exception(可捕获所有标准异常)
int main() {
try {
// 尝试分配一个极大的内存(必然失败,触发异常)
// 注意:size_t 是无符号整数,这里用一个超大值(如 SIZE_MAX)
const size_t huge_size = static_cast<size_t>(-1); // 等价于 SIZE_MAX(最大无符号值)
int* p = new int[huge_size]; // 分配失败,抛出 std::bad_alloc
// 如果分配成功,后续逻辑(这里不会执行)
std::cout << "内存分配成功!" << std::endl;
delete[] p; // 释放数组内存(注意用 delete[],对应 new[])
}
// 捕获具体的 bad_alloc 异常(推荐:精准处理内存分配失败)
catch (const std::bad_alloc& e) {
// e.what() 返回异常的描述信息(实现相关,如 "std::bad_alloc")
std::cerr << "内存分配失败:" << e.what() << std::endl;
}
// 捕获所有标准异常(兜底:防止其他异常未被捕获)
catch (const std::exception& e) {
std::cerr << "发生异常:" << e.what() << std::endl;
}
// 捕获所有未知异常(最后兜底)
catch (...) {
std::cerr << "发生未知异常!" << std::endl;
}
return 0;
}
malloc/free和new/delete的区别
最后总结他们的区别:
malloc/free和new/delete的共同点是:都是从堆上申请空间,并且需要用户手动释放。不同的地方是:
- malloc和free是函数,new和delete是操作符
- malloc申请的空间不会初始化,new可以初始化
- malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可,如果是多个对象,[]中指定对象个数即可
- malloc的返回值为void*, 在使用时必须强转,new不需要,因为new后跟的是空间的类型
- malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需要捕获异常
- 申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理释放