当前位置: 首页 > news >正文

C++学习笔记——内存管理

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

选择题: 

选项: A.栈 B.堆 C.数据段(静态区) D.代码段(常量区) 
1.globalVar在哪里?____ 
2.staticGlobalVar在哪里?____ 
3.staticVar在哪里?____ 
4.localVar在哪里?____ 
5.num1 在哪里?____ 
6.char2在哪里?____ 
7.*char2在哪里?___ 
8.pChar3在哪里?____ 
9.*pChar3在哪里?____ 
10.ptr1在哪里?____ 
11.*ptr1在哪里?____

问题分析:

选项说明:​​
A. 栈:存储非静态局部变量、函数参数、返回值等
B. 堆:动态内存分配(malloc/calloc/realloc)
C. 数据段(静态区):存储全局数据和静态数据(包括全局变量和static修饰的变量)
D. 代码段(常量区):存储可执行代码和只读常量(如字符串常量)
具体分析:​​
1.globalVar:全局变量 → 数据段(静态区)→ ​C​
2.staticGlobalVar:静态全局变量 → 数据段(静态区)→ ​C​
3.staticVar:静态局部变量(虽在函数内,但由static修饰)→ 数据段(静态区)→ ​C​
4.localVar:非静态局部变量 → 栈 → ​A​
5.num1:非静态局部数组(在函数内)→ 栈 → ​A​
6.char2:非静态局部字符数组(在函数内)→ 栈 → ​A​
注意:char2[] = "abcd" 在栈上分配数组并初始化,字符串"abcd"本身在常量区,但数组副本在栈上)
7.*char2:解引用char2(即char2数组的第一个元素)→ 存储在栈上(因为char2本身在栈)→ ​A​
8.pChar3:非静态局部指针变量(在函数内)→ 栈 → ​A​
9.*pChar3:解引用pChar3(指向字符串常量"abcd"的首元素,里面存首元素的地址)→ 字符串常量存储在代码段(常量区)→ ​D​
10.ptr1:非静态局部指针变量(在函数内)→ 栈 → ​A​
11.*ptr1:解引用ptr1(指向malloc动态分配的内存)→ 堆 → ​B
图片示例:

2、C语言中动态内存管理方式:malloc/calloc/realloc/free

void Test()
{ int* p2 = (int*)calloc(4, sizeof(int));int* p3 = (int*)realloc(p2, sizeof(int) * 10);// 这里需要free(p2)吗? free(p3);
}

这里需要free(p2)吗?

答:不需要,而且绝对不能这样做。


原因分析:​​

realloc 函数的功能是重新分配之前由 malloc, calloc, 或 realloc 所分配的内存块(p2所指向的内存块)的大小。

当调用** int* p3 = (int*)realloc(p2, sizeof(int)*10); **时,会发生以下两种情况之一:
情况一(原地扩容):​​如果 p2 指向的内存块后面有足够的空闲空间,realloc 会尝试在原地扩大这块内存。函数返回的地址值 p3 ​等于传入的地址值 p2。此时,p2 和 p3 指向同一个内存块。

情况二(异地迁移):​​如果原地没有足够空间,realloc 会在堆的另一块区域分配一个足够大的新内存块(sizeof(int)*10),将旧内存块(p2指向的)中的数据原样拷贝过来,然后自动释放旧内存块。函数返回的地址值 p3 是一个新地址,与 p2 不同。

关键在于,​无论哪种情况,realloc 调用后,之前由 p2 指向的旧内存块都已经被系统接管,程序员不再拥有它的所有权,也不应该再尝试去释放它。​​

在情况一中,这块内存被扩大了,需要通过 p3 来管理。
在情况二中,这块内存已经被 realloc 函数内部自动 free 掉了。

因此,代码中只需要 free(p3) 即可,它释放的就是当前有效的那一块内存。如果在 realloc 后再 free(p2),会导致双重释放的错误,这是一种未定义行为,通常会导致程序崩溃。

牢记:永远只 free 由 malloc, calloc, realloc 返回的最新指针。在一次成功的 realloc 调用后,旧的指针就应该被立即视为无效,不应再使用或释放。

面试题总结

1. malloc/calloc/realloc的区别?

特性

malloc

calloc

realloc

功能

分配指定大小的内存块

分配并初始化指定数量、大小的内存块

调整已分配内存块的大小

函数原型

void* malloc(size_t size);

void* calloc(size_t num, size_t size);

void* realloc(void* ptr, size_t new_size);

初始化

不初始化内存内容,内容是随机值

初始化内存内容为全零

根据情况,新增加的内存区域不会被初始化

参数

size:要分配的字节数

num:元素个数

size:每个元素的字节数

ptr:要调整的旧内存指针

new_size:新的总字节数

使用场景

需要分配内存且不关心初始值,或准备自行初始化时

需要分配数组或结构体并希望初始值全为零时

需要扩大或缩小之前动态分配的内存时

2. malloc的实现原理?

glibc中malloc实现原理

3、C++内存管理方式

C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力,而且使用起来比较麻烦,因
此C++又提出了自己的内存管理方式:通过new和delete操作符进行动态内存管理。

3.1 new/delete操作内置类型

void Test()
{// 动态申请一个int类型的空间 int* ptr4 = new int;        // 分配空间,值未初始化(是随机值)delete ptr4;                // 释放空间// 动态申请一个int类型的空间并初始化为10 int* ptr5 = new int(10);    // 分配空间并初始化为10delete ptr5;                // 释放空间// 动态申请3个int类型的空间 int* ptr6 = new int[3];     // 这里3表示对象个数,值都未初始化delete[] ptr6;              // 释放连续的数组空间// 动态申请3个int类型的空间并初始化 int* ptr7 = new int[3] {1,2,3}; // 值初始化为1,2,3delete[] ptr7;                  // 释放连续的数组空间
}

1.初始化​:new 可以通过 new int(10) 的方式对单个元素进行初始化,而 malloc 无法方便地做到这一点。
​2.数组操作​:使用 new[] 来分配数组,使用 delete[] 来释放数组。​必须匹配使用,否则行为未定义(尤其是对自定义类型,会导致析构函数调用次数错误)。
​3.对于内置类型​(如 int),new/delete 和 malloc/free 在功能上几乎没有区别,都是分配和释放内存。但 new 的语法更简单,且支持初始化。

3.2 newdelete操作自定义类型

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);              // 1. 分配内存 2. 调用构造函数 A(1)free(p1);                      // 只释放内存,不调用析构函数delete p2;                     // 1. 调用析构函数 2. 释放内存// 对比二:内置类型(行为几乎相同)int* p3 = (int*)malloc(sizeof(int)); int* p4 = new int; free(p3); delete p4; // 对比三:对象数组A* p5 = (A*)malloc(sizeof(A)*10); // 分配10个A对象的内存,全部未构造A* p6 = new A[10];                // 分配内存,并调用10次默认构造函数free(p5);                         // 直接释放内存,10个对象都未析构delete[] p6;                      // 调用10次析构函数,然后释放内存return 0; 
}

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

4. operator newoperator delete函数(重点)

new/delete ​操作符​与operator new/operator delete全局函数​之间的关系和区别。

1.new 和 delete 是 C++ 语言中的操作符(关键字)​,供程序员直接使用来进行动态内存分配和释放。
2.operator new 和 operator delete 是系统提供的全局函数,是 new 和 delete 操作符在底层实现时所调用的工具。

​3.调用关系​:
new 操作符的底层行为可以分解为两步:
a. 调用 operator new 函数来分配内存。
b. 在分配好的内存上调用对象的构造函数。
delete 操作符的底层行为也可以分解为两步:
a. 调用对象的析构函数。
b. 调用 operator delete 函数来释放内存。

​4.功能定位​:
operator new/operator delete 的核心职责只是管理内存的分配和释放​(类似于更高级的 malloc 和 free),它们不负责调用构造函数和析构函数。调用构造和析构是 new/delete 操作符自己的逻辑。

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

operator new 函数库代码

void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{// try to allocate size bytesvoid *p;while ((p = malloc(size)) == 0) // 底层调用malloc尝试分配size字节的内存{if (_callnewh(size) == 0) // 如果malloc失败,检查用户是否设置了“new-handler”函数{// report no memory// 如果用户没有设置new-handler,或者new-handler也无法解决问题,则抛出异常static const std::bad_alloc nomem;_RAISE(nomem); // 抛出std::bad_alloc类型异常}// 如果用户设置了new-handler(_callnewh返回非0),循环会继续,再次尝试malloc}return (p); // 分配成功,返回指向内存的指针
}

operator new底层调用 malloc

operator new 的本质是 malloc 的一个封装。它的主要工作就是调用 malloc(size) 来分配指定大小的内存。

失败处理机制(与malloc的关键区别)​​:
1.malloc 失败时直接返回 NULL 空指针。
2.operator new 失败时,会进入一个循环。它首先检查用户是否通过 std::set_new_handler 设置了一个new-handler​ 函数。如果没有设置new-handler或它也无法解决问题,抛出 std::bad_alloc 异常。


operator delete 函数库代码

void operator delete(void *pUserData)
{... // 一些调试和线程安全代码if (pUserData == NULL) // 遵循C++标准:delete空指针是安全的,直接返回return;..._free_dbg( pUserData, _NORMAL_BLOCK ); // 底层调用free的调试版本...return;
}
// free的本质
#define free(p) _free_dbg(p, _NORMAL_BLOCK)

operator delete底层调用 free​

1.operator delete 的本质是 free 的一个封装。它的主要工作就是调用 free(pUserData) 来释放内存。
​2.operator delete 检查传入的指针是否为空 (NULL),如果是则直接返回。这保证了 delete nullptr; 是安全的操作。

上述两个全局函数的总结:

通过上述两个全局函数的实现知道,operator new 实际也是通过malloc来申请空间,如果
malloc申请空间成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施
就继续申请,否则就抛异常。operator delete 最终是通过free来释放空间的。

5、newdelete的实现原理

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表达式(placement-new) (了解)

概念​:

定位 new 是一种特殊形式的 new,它不在堆上分配新的内存,而是在已经分配好的原始内存空间上调用构造函数来初始化(创建)一个对象。

​使用格式​:

1.new (place_address) type
2.new (place_address) type(initializer-list) (带初始化列表,用于有参构造函数)
3.place_address 必须是一个指针,指向预先分配好的内存块。

​使用场景​:

最主要的用途是配合内存池。内存池为了提高效率,会预先分配大块内存,然后从中切分给小对象使用。这些内存块是“原始”的,没有经过构造函数的初始化。定位 new 就被用来在这些原始内存上构造对象。


注意​:由于对象是手动构造的,其析构函数也必须手动调用。内存的释放也同样需要根据其分配方式(如 malloc 或 operator new)来手动释放。

class A
{
public:A(int a = 0): _a(a){cout << "A():" << this << endl;}~A(){cout << "~A():" << this << endl;}
private:int _a;
};
// 定位new/replacement new 
int main()
{// p1现在指向的只不过是与A对象相同大小的一段空间,还不能算是一个对象,// 因为构造函数没有执行//使用 malloc 分配原始内存A* p1 = (A*)malloc(sizeof(A));new(p1)A; // 使用定位new在p1指向的内存上构造A对象。// 注意:如果A类的构造函数有参数时,此处需要传参 p1->~A(); // 手动调用析构函数,销毁对象。对象所占的内存还在,但对象的生命周期结束free(p1); // 释放p1指向的内存块//使用 operator new 分配原始内存A* p2 = (A*)operator new(sizeof(A)); // 使用operator new分配内存new(p2)A(10); // 使用定位new在p2指向的内存上构造A对象p2->~A();     // 手动调用析构函数operator delete(p2); // 使用operator delete释放内存return 0;
}

注意:​使用 malloc/free 或 operator new/operator delete。必须确保分配和释放的方式匹配(malloc 配 free, operator new 配 operator delete)。在对象周期结束时,需要手动调用析构函数,销毁对象。此时,对象所占的内存还在,但对象的生命周期结束。使用匹配释放的方式才会释放之前对象所占的内存空间。

7、malloc/freenew/delete的区别(重点)

malloc/free和new/delete的共同点是:都是从堆上申请空间,并且需要用户手动释放。

不同的地方是(用法,核心特性,原理):
1. mallocfree函数newdelete操作符
2. malloc申请的空间不会初始化,new可以初始化
3. malloc申请空间时,需要手动计算空间大小并传递new只需在其后跟上空间的类型即可,如果是多个对象,[]中指定对象个数即可。
4. malloc返回值为void*, 在使用时必须强转new不需要,因为new后跟的是空间的类型。
5. malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需要捕获异常
6. 申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new在申请空间后会调用构造函数完成对象的初始化delete在释放空间前会调用析构函数完成(最主要不同)
 

http://www.dtcms.com/a/392456.html

相关文章:

  • AI热点周报(09.14~09.20):Gemini集成到Chrome、Claude 强化记忆、Qwen3-Next快速落地,AI走向集成化,工程化?
  • 网络服务阶段——作业
  • OpenLayers地图交互 -- 章节三:选择交互详解
  • RocksDB:C++中的RAII锁应用解析
  • Linux920 RHEL 8 YUM配置;root密码;文件夹 磁盘分区 磁盘
  • yarn命令介绍(替代npm命令的JavaScript包管理工具)
  • MFC中开源布局库---ResizableLib
  • Scade 6 编译原理的参考实现 - LustreC
  • MFC List 控件详解:高效数据展示与管理
  • 从根到叶的二进制数之和(霍纳法则)
  • 隐私与合规内建:Python医疗AI编程中的SBOM、依赖监测与威胁建模实践分析(上)
  • 基于实战:如何高效调用陌讯AIGC检测RESTful API进行批量内容审核
  • 如何用kimi写一个最小excel软件
  • Ansible-script模块
  • ansible批量给网络设备下发配置
  • 使用 Bright Data Web Scraper API Python 高效抓取 Glassd
  • uni-app 用scroll-view实现横向滚动
  • Kafka 图形界面客户端工具
  • 【开题答辩全过程】以 Php产品报价系统的设计与实现为例,包含答辩的问题和答案
  • 软件测试基础知识(网络协议)
  • 手机中的轻量化 AI 算法:智能生活的幕后英雄
  • wo店模式兴起旧模式式微:本地生活服务市场的深度变革
  • 服务器磁盘空间满了怎么办?阿里云ECS清理与云盘扩容教程
  • OpenAI推出更擅长AI代理编码的GPT-5-Codex,与Claude code有何区别?国内怎么使用到Codex呢?
  • GPT-5 深度测试报告:前端编程能力专项评估
  • AIGC发展:从GPT-1到GPT-4的技术演进与行业革新
  • 从AI生成到学术表达:如何有效降低AI率,实现论文合规化写作
  • 【国二】C语言选择题精华速记
  • 聊聊和AutoDL的故事
  • 【状态机实现】前置——设计模式中的孪生兄弟(状态模式和策略模式)