目录
1. C/C++内存分布
练习:
2. C语言动态内存管理方式
2.1 malloc/calloc/realloc的区别
2.2 malloc的实现原理
2.3 内存块分布与扩容
3. C++动态内存管理方式
3.1 new/delete操作类内置类型
1. new操作内置类型
2. delete操作内置类型
3.2 new/delete操作类自定义类型
1. new操作自定义类型
2. delete操作自定义类型
4. operator new与operator delete函数
4.1 operator new 函数
4.2 operator delete函数
4.3 与new/delete操作符的关系
5. new和delete的实现原理
5.1 内置类型
5.2 自定义类型
6. new/delete 和 malloc/free的区别
1. 所属语言
2. 操作对象与功能
3. 数组操作
4. 内存不足处理
5. 重载与自定义
7. 总结:
1. C/C++内存分布
在C和C++中 , 程序的内存分布通常可划分为几个主要区域 , 这些区域各自承担不同的功能 , 具体如下:
1. 栈(Stack)
- 特点 : 由编译器自动管理 , 空间较小(通常几MB) , 遵循“先进后出”原则。
- 存储内容 : 函数的局部变量 , 函数参数 , 返回地址等。
- 示例 : 函数内部定义的 int a = 10;,a就存储在栈中 , 函数执行结束后会自动释放。
2. 堆(Heap)
- 特点 : 由程序员手动管理(C中用 malloc / free , C++中用 new / delete) , 空间较大(可达GB级别) , 分配和释放需要显式操作。
- 存储内容 : 动态分配的内存 , 比如动态创建的对象、数组等。
- 示例 : int* p = new int[10]; , 数组的内存就位于堆中 , 需用 delete[] p 手动释放 , 否则可能导致内存泄漏。
3. 全局/静态存储区(Global/Static Storage Area)(数据段)
- 特点 : 程序运行期间一直存在 , 由编译器管理 , 程序结束后自动释放。
- 存储内容:
- 全局变量(定义在函数外的变量);
- 静态变量(用 static 修饰的变量 , 包括全局静态变量和局部静态变量)。
- 示例 : int g_var = 20;(全局变量) , static int s_var = 30;(静态变量) , 都存储在此区域。
4. 常量存储区(Constant Storage Area)(代码段)
- 特点 : 存放常量 , 内容不可修改 , 程序结束后释放。
- 存储内容 : 字符串常量(如"hello") , const 修饰的常量(全局或静态的 const 变量 , 局部const 变量可能在栈中)。
- 示例 : const int c_var = 40;(全局常量) , char* str = "world";("world" 存于常量区)。
5. 代码区(Code Segment/Text Segment)
- 特点 : 存放程序的机器指令(二进制代码) , 通常为只读 , 以防止意外修改。
- 作用 : CPU从这里读取指令并执行程序。
总结来说 , 栈和堆用于动态管理运行时数据 , 全局/静态区和常量区存储生命周期较长的数据 , 代码区则负责存储程序的执行指令 , 这些区域共同构成了C和C++程序的内存布局。
练习:
以下是对每个变量内存位置的详细分析:
- globalVar : 属于全局变量 , 存储在数据段(静态区 , 选项C)。全局变量在程序整个运行周期都存在 , 由编译器管理其内存 , 程序结束时自动释放。
- staticGlobalVar : 是全局静态变量,同样存储在数据段(静态区 , 选项C)。静态全局变量作用域限制在定义的文件内 , 但存储区域和全局变量一样 , 在数据段 , 生命周期贯穿程序运行。
- staticVar : 为函数内的静态变量 , 存储在数据段(静态区 , 选项C)。函数内的静态变量 , 在第一次函数调用时初始化 , 之后一直存在于数据段 , 直到程序结束。
- localVar : 是函数内的局部变量 , 存储在栈(选项A)。局部变量在函数调用时在栈上分配空间 , 函数执行完毕 , 栈空间自动释放。
- num1 : 是函数内的局部数组 , 存储在栈(选项A)。局部数组属于局部变量 , 在栈上分配内存 , 函数结束后栈空间回收。
- char2 : char2 是函数内的字符数组 , 存储在栈(选项A);而 "abcd" 作为字符串常量 , 存储在代码段(常量区 , 选项D) , 数组 char2 是在栈上 , 将常量区的字符串内容拷贝过来。
- *char2 : char2 是栈上的数组 , *char2 是数组的第一个字符 , 所以存储在栈(选项A)。
- pChar3 : pChar3 是指针变量 , 存储在栈(选项A) , 它指向的是字符串常量 "abcd"。
- *pChar3 : pChar3 指向的字符串常量 "abcd" 存储在代码段(常量区 , 选项D) , 所以*pChar3(即字符串的字符)在代码段(常量区 , 选项D)。
- ptr1 : ptr1 是指针变量 , 存储在栈(选项A) , 它指向堆上的内存。
- *ptr1 : ptr1 通过 malloc 动态分配的内存位于堆(选项B) , 所以*ptr1(即所指向的内存区域)在堆(选项B)。
2. C语言动态内存管理方式
2.1 malloc/calloc/realloc的区别
在前面的C语言中 , 我们学习了C语言的动态动态内存管理方式 , 其中主要学了在malloc , calloc , realloc 和free等函数 , 下面我们再简单回顾一下它们之间的区别:
在C语言中 , malloc , calloc , realloc 和free是实现动态内存管理的重要函数 , 它们各自有不同的功能和使用方式:
1. malloc函数
- 函数原型:void* malloc(size_t size);
- 功能:在堆内存中分配指定字节数的连续内存空间。函数返回一个指向分配内存起始地址的指针 , 如果分配失败(例如内存不足) , 则返回NULL 。
- 注意事项:
- malloc 分配的内存空间中的值是未初始化的 , 可能是任意值。
- 使用完 malloc 分配的内存后 , 必须调用 free 函数释放 , 否则会导致内存泄漏。
- 由于 malloc 返回的是 void* 类型指针 , 在赋值给其他类型指针时 , 需要进行强制类型转换。
2. calloc函数
- - 函数原型:void* calloc(size_t num, size_t size);
- - 功能 : 在堆内存中分配 num 个大小为 size 字节的连续内存空间 , 并将这些空间初始化为0。它返回一个指向分配内存起始地址的指针 , 如果分配失败 , 则返回 NULL 。
- 注意事项:
- 相比 malloc , calloc会自动进行初始化操作 , 适合需要初始值为0的场景 , 不过也因为多了初始化操作 , 在性能上会有一定开销。
- 同样 , 使用完后要调用 free 释放内存。
3. realloc函数
- 函数原型: void* realloc(void* ptr, size_t size);
- 功能:用于重新分配已分配的内存空间。ptr 是指向之前由 malloc , calloc 或 realloc 分配的内存块的指针 , size 是新的内存块大小。如果 ptr 为 NULL , 则 realloc 相当于malloc;如果 size 为0且 ptr 不为NULL , 则释放 ptr 指向的内存块 , 相当于 free(ptr) 。
- 注意事项:
- realloc 可能会移动原来内存块的位置 , 因为它要根据当前内存的使用情况和新的大小来决定是否重新分配一块新的连续内存空间。所以在调用 realloc 后 , 要使用其返回的新指针 , 而不能再使用原来的指针。
- 如果重新分配失败 , 原来 ptr 指向的内存块不会被释放 , 仍然有效。
4. free函数
- 函数原型:void free(void* ptr);
- 功能:释放由 malloc , calloc 或 realloc 分配的内存空间 , 让系统可以回收并重新利用这部分内存。ptr 是指向要释放的内存块的指针。
- 使用示例:在前面介绍 malloc , calloc , realloc 的示例中都有使用free释放内存的操作。
- 注意事项:
- 只能释放由 malloc , calloc , realloc 分配的内存 , 释放其他内存会导致未定义行为。
- 不能多次释放同一块内存 , 也不能释放 NULL 指针(虽然释放 NULL 指针不会报错 , 但也无实际意义)。
#include <stdio.h>
#include <stdlib.h> // 包含动态内存管理函数的头文件int main() {int *ptr1, *ptr2, *ptr3;// 1. 使用 malloc 分配内存(未初始化)ptr1 = (int*)malloc(3 * sizeof(int)); // 分配3个int的空间// 2. 使用 calloc 分配内存(自动初始化为0)ptr2 = (int*)calloc(4, sizeof(int)); // 分配4个int的空间// 3. 使用 realloc 调整已分配的内存(基于ptr1扩展)ptr3 = (int*)realloc(ptr1, 5 * sizeof(int)); // 将ptr1的3个int扩展为5个// 4. 释放所有动态分配的内存free(ptr3); // 释放realloc返回的新地址(原ptr1已被覆盖,无需重复释放)free(ptr2); // 释放calloc分配的内存return 0;
}
2.2 malloc的实现原理
malloc 是C语言中用于动态分配内存的函数 , 其工作原理可简要概括为:
- 内存池管理:程序运行时会有一块预设的堆内存区域(内存池) , malloc 负责管理这块区域 , 而非直接向操作系统申请内存。
- 分配过程:当调用 malloc 申请指定大小的内存时 , 它会在内存池中查找足够大的空闲内存块 , 划分出所需大小的部分 , 返回指向该部分的指针 , 并记录该内存块的使用信息(如大小、状态)。
- 内存池扩容:若内存池中没有足够的空闲内存 , malloc 会通过系统调用(如 Linux 中的 brk 或 mmap)向操作系统申请更多内存 , 扩充内存池后再进行分配。
- 配合释放操作:当通过 free 释放内存时 , malloc 会根据之前记录的信息回收内存块 , 标记为空闲 , 并可能合并相邻的空闲块 , 以减少内存碎片 , 提高后续分配效率。
2.3 内存块分布与扩容
动态内存分配中:
- 内存块分布:已分配的内存块和空闲块在堆区混杂存在。当申请新内存时 , 若有合适大小的空闲块 , 直接分配;
- 扩容:若要给已分配的内存块扩容(如用 realloc ) , 如果原内存块后有足够空闲空间 , 直接原地扩大;若没有 , 就新找一块足够大的连续空间 , 复制原数据后释放旧空间 , 返回新地址。
3. C++动态内存管理方式
C语言的动态内存管理方式在C++中仍然可以继续使用 , 但是有些地方就会显得无能为力了 , 而且使用起来比较麻烦 , 因此C++又提出了一种属于自己的动态内存管理方式 , 通过new和delete操作符进行动态内存管理。
3.1 new/delete操作类内置类型
在 C++ 中 , new 和 delete 操作内置类型(如 int , double , char 等)时 , 流程相对简单 , 不涉及对象的构造和析构(因为内置类型没有构造函数和析构函数) , 核心是内存的分配与释放:
1. new操作内置类型
分配单个元素:如 int* p = new int;
- 步骤 : 向堆区申请一块能容纳 int 类型的内存空间 , 返回指向该空间的指针。
- 注意 : 默认不初始化 , 内存中的值是随机的;若要初始化 , 可写成 int* p = new int(10); , 此时内存会被初始化为10。
分配数组:如 int* arr = new int[5];
- 步骤 : 向堆区申请能容纳5个 int 的连续内存空间 , 返回指向首元素的指针。
- 注意 : 默认不初始化;若要初始化所有元素为 0 , 可写成 int* arr = new int[5](); 。
2. delete操作内置类型
释放单个元素:如 delete p;
- 步骤:直接释放 p 指向的堆内存(无需额外清理 , 因无析构函数) , p 变为野指针(建议置为 nullptr)。
释放数组:如 delete[] arr;
- 步骤:释放 arr 指向的整个连续数组内存 , 必须用 delete[] 匹配 new[] (虽然内置类型下用 delete 可能不报错 , 但会导致行为未定义 , 是错误写法)。
简言之 , 内置类型的 new / delete 核心是“分配/释放堆内存” , 比自定义类型少了构造/析构步骤 , 但仍需严格匹配 new 与 delete , new[] 与 delete[]。
3.2 new/delete操作类自定义类型
new 和 delete 操作自定义类型时 , 会完整包含“内存管理”和“对象生命周期管理”两个环节 , 具体流程如下:
1. new操作自定义类型
以类 A 为例分配单个对象:如 A* p = new A;
- 1. 先调用底层内存分配函数(类似 malloc) , 在堆区申请一块能容纳 A 类型对象的内存。
- 2. 自动调用 A 的构造函数(根据参数匹配对应构造函数 , 如无参数则调用默认构造函数) , 初始化这块内存中的对象(例如初始化成员变量 , 申请资源等)。
- 3. 返回指向该对象的指针 p。
分配数组:如 A* arr = new A[3];
- 1. 申请能容纳3个 A 对象的连续堆内存。
- 2. 依次调用3次 A 的构造函数(每个元素对应一次) , 初始化数组中的每个对象。
2. delete操作自定义类型
释放单个对象:如 delete p;
- 1. 先调用 p 指向对象的析构函数( A::~A() ) , 清理对象内部资源(例如释放成员变量指向的堆内存 , 关闭文件等)。
- 2. 调用底层内存释放函数(类似 free ) , 将对象占用的堆内存归还给系统。
- 3. 指针 p 变为野指针 , 建议置为 nullptr。
释放数组:如 delete[] arr;
- 1. 先按相反顺序依次调用数组中每个A对象的析构函数(共 3 次) , 清理所有元素的资源。
- 2. 释放整块连续内存。
- 注意 : 必须用 delete[] 匹配 new[] , 否则会导致部分对象析构函数不被调用 , 造成资源泄漏。
核心区别:自定义类型的 new / delete 比内置类型多了构造函数初始化对象和析构函数清理资源的步骤 , 这是C++面向对象特性在内存管理中的直接体现 , 确保对象从创建到销毁的完整生命周期管理。
//new和delete操作自定义类型
#include<iostream>
using namespace std;class A
{
public:A(int a = 0) //构造函数: _a(a){cout << "A():" << this << endl;}~A() //析构函数{cout << "~A():" << this << endl;}
private:int _a;
};int main()
{// new/delete 和 malloc/free最大区别是 new/delete对于【自定义类型】除了开空间还会调用构造函数和析构函数//自定义类型A* p1 = (A*)malloc(sizeof(A));free(p1);A* p2 = new A(1);delete p2;//内置类型是几乎是一样的int* p3 = (int*)malloc(sizeof(int)); free(p3);int* p4 = new int;delete p4;A* p5 = (A*)malloc(sizeof(A) * 10);free(p5);A* p6 = new A[10];delete[] p6;return 0;
}
输出结果:
4. operator new与operator delete函数
new和delete是用户进行动态内存申请和释放的操作符 , operator new 和operator delete是系统提供的全局函数 , new在底层调用operator new全局函数来申请空间 , delete在底层通过operator delete全局函数来释放空间。可以理解为operator new 和 operator delete 是 new 和 delete 操作符的底层支撑 , 主要负责内存的分配与释放(不涉及对象的构造和析构):
4.1 operator new 函数
- 功能:负责在堆上分配指定大小的内存块 , 与 C 语言中的 malloc 类似 , 但行为有差异(operator new 分配失败时默认抛出 std::bad_alloc 异常 , 而 malloc 则会返回 NULL)。
- 函数原型(简化版) : void* operator new(size_t size); 其中 size 是要分配的内存字节数。
- 使用示例:
#include <iostream> #include <new> // 包含相关异常定义等int main() {// 分配能容纳一个 int 类型的内存int* p = (int*)operator new(sizeof(int));if (p == nullptr) {std::cout << "内存分配失败" << std::endl;return 1;}*p = 10;std::cout << *p << std::endl;operator delete(p);return 0; }
- 特点:
- 只分配内存 , 不调用对象的构造函数(这是和 new 操作符的关键区别 , new 会在 operator new 分配内存后调用构造函数)。
- 可以被重载 , 用户能自定义内存分配的方式(比如使用内存池等)。
4.2 operator delete函数
- 功能 : 负责释放由 operator new 分配的内存块 , 与 C 语言中的 free 类似。
- 函数原型(简化版) : void operator delete(void* ptr); 其中 ptr 是要释放的内存块指针。
- 使用示例 : 如上面的示例 , 在使用 operator new 分配内存后 , 通过 operator delete(p) 释放内存。
- 特点:
- 只释放内存 , 不调用对象的析构函数(这是和 delete 操作符的关键区别 , delete 会在调用 operator delete 释放内存前调用析构函数)。
- 同样可以被重载 , 用户能自定义内存释放的逻辑。
4.3 与new/delete操作符的关系
- new 操作符的执行过程 : 先调用 operator new 分配内存 , 然后调用对象的构造函数初始化对象。
- delete 操作符的执行过程 : 先调用对象的析构函数清理对象 , 然后调用 operator delete 释放内存。
- 简单来说 , operator new 和 operator delete 是更底层的内存分配/释放工具 , new 和 delete 则是在它们的基础上 , 结合了对象的构造和析构操作 , 更符合 C++ 面向对象的特性。
5. new和delete的实现原理
5.1 内置类型
- 如果申请的是内置类型的空间 , new和malloc , delete和free基本类似 , 不同的地方是:new/delete申请和释放的是单个元素的空间 , new[]和delete[]申请的是连续空间 , 而且new在申请空间失败时会抛异常 , malloc会返回NULL。
5.2 自定义类型
new的原理
- 1. 调用operator new函数申请空间
- 2. 在申请的空间上执行构造函数 , 完成对象的构造
delete的原理
- 1. 在空间上执行析构函数 , 完成对象中资源的清理工作
- 2. 调用operator delete函数释放对象的空间
new T[N]的原理
- 1. 调用operator new[]函数 , 在operator new[]中实际调用operator new函数完成N个对象空间的申请
- 2. 在申请的空间上执行N次构造函数
delete[]的原理
- 1. 在释放的对象空间上执行N次析构函数 , 完成N个对象中资源的清理
- 2. 调用operator delete[]释放空间 , 实际在operator delete[]中调用operator delete来释放空间
6. new/delete 和 malloc/free的区别
new/delete 和 malloc/free 都是 C++(及C)中用于动态内存管理的工具 , 主要区别如下:
1. 所属语言
- malloc / free 是 C 语言的标准库函数 , 在 C++ 中也可使用 , 但更偏向于底层内存操作。
- new / delete 是 C++ 的操作符 , 是 C++ 面向对象特性在内存管理上的体现。
2. 操作对象与功能
- malloc / free :
- 仅负责内存的分配与释放 , 不涉及对象的构造和析构(因为C是面向过程语言 , 无“对象”概念)。
- malloc 需手动计算内存大小(如 malloc(sizeof(int)) ) , 返回 void* , 使用时通常需要强制类型转换。
- free 只需传入要释放的内存指针。
- new / delete :
- 不仅分配/释放内存 , 还会自动调用对象的构造函数( new 时)和析构函数( delete 时)(针对自定义类型)。
- new 无需手动计算类型大小(如 new int 会自动分配 int 大小的内存) , 直接返回对应类型的指针 , 无需强制类型转换。
- delete 只需传入对象指针。
3. 数组操作
- malloc / free :
- 分配数组需手动计算总内存大小(如 malloc(5 * sizeof(int)) ) , 释放时直接 free 指针 , 不涉及数组元素的“逐个清理”(因为无析构函数)。
- new / delete :
- 分配数组用 new[] (如 new int[5] ) , 会为数组中每个元素(若为自定义类型)调用构造函数;释放数组用 delete[] , 会为每个元素调用析构函数 , 然后释放整块内存。若用 delete 释放 new[] 分配的数组(自定义类型) , 会因析构函数调用不完整导致内存泄漏或错误。
4. 内存不足处理
- malloc : 内存不足时返回 NULL , 需手动检查返回值。
- new : 内存不足时默认抛出 std::bad_alloc 异常 , 可通过异常处理机制捕获 , 也可自定义 new 的行为(如设置内存不足的回调)。
5. 重载与自定义
- malloc / free : 不可重载 , 行为固定。
- new / delete : 可以重载 , 用户可自定义内存分配和释放的逻辑(比如使用内存池等) , 更灵活地满足特定需求。
简单来说 , new / delete 是更“面向对象”的内存管理方式 , 封装了对象构造/析构的逻辑;而 malloc / free 更底层 , 仅关注内存块的分配与释放。在 C++ 中 , 更推荐使用 new / delete (尤其是处理自定义类型时) , 以利用其对对象生命周期的管理能力。
7. 总结:
本文系统介绍了C/C++程序的内存分布与动态内存管理机制。内存主要分为栈、堆、全局/静态区、常量区和代码区五部分 , 各自承担不同功能。C语言通过malloc/calloc/realloc/free进行动态内存管理 , 而C++引入了更高级的new/delete操作符 , 不仅能分配内存还能自动调用构造/析构函数。文章详细对比了malloc/free与new/delete的区别 , 并深入解析了operator new/delete的实现原理。关键在于:C++的new/delete是面向对象的内存管理方式 , 而malloc/free只进行原始内存操作。在C++开发中 , 特别是处理自定义类型时 , 应优先使用new/delete以确保完整的对象生命周期管理。
最后 , 感谢大家的观看!