【C++指南】C++内存管理 深度解析
💓 博客主页:倔强的石头的CSDN主页
📝Gitee主页:倔强的石头的gitee主页
⏩ 文章专栏:《C++指南》
期待您的关注
目录
引言
一、C++ 内存管理概述
二、C++内存区域划分
三、C++ 内存管理方式
🍃1.自动内存管理(栈内存)
🍃2.静态内存管理(全局和静态变量)
🍃3.动态内存管理(堆内存)
四、C++ 动态内存管理方式
🍃1.new 和 delete 的用法
🍃2. 使用 new 和 delete 的注意事项
🍃3. new、delete 与 malloc、free 的区别
五、内存管理注意事项
引言
在 C++ 编程的世界里,内存管理犹如大厦之基石,至关重要。有效的内存管理不仅关乎程序的性能,更与程序的稳定性和安全性紧密相连。错误的内存操作可能引发难以察觉的漏洞,甚至导致程序崩溃。C++ 赋予了程序员精细掌控内存的能力,从变量的存储分配到动态内存的申请与释放,每一个环节都充满挑战与机遇。
本文将深入探讨 C++ 内存管理的核心概念、常用技术以及最佳实践,为你揭开高效内存管理的神秘面纱。
一、C++ 内存管理概述
在 C++ 中,内存管理是程序开发中至关重要的一环。由于 C++ 允许程序员直接操作内存,这既赋予了极大的灵活性,也带来了一定的复杂性和风险。高效且正确的内存管理对于编写高性能、稳定可靠的 C++ 程序起着关键作用。
C++ 的内存管理涉及到内存的分配和释放。如果内存分配不当或者释放不及时,可能会导致内存泄漏、悬空指针等问题,从而影响程序的正确性和性能。
二、C++内存区域划分
C++和C语言的内存区域划分是相同的,都包括栈区、堆区、全局/静态区、常量区和代码区。这些区域在功能、生命周期、管理方式和特性上都有所不同,共同支持着程序的正常运行。
相关细节可以阅读我的上一篇文章:
【C语言指南】C语言内存管理 深度解析_c语言内存映射-CSDN博客
三、C++ 内存管理方式
🍃1.自动内存管理(栈内存)
局部变量(包括函数内的变量)通常使用这种分配方式
- 原理:
- C++ 中的自动内存管理主要依赖栈(Stack)。栈是一种数据结构,它按照后进先出(LIFO)的原则进行操作。当一个函数被调用时,编译器会在栈上为函数的局部变量和函数参数自动分配内存。这些变量的生命周期与函数的执行周期紧密相关。当函数开始执行时,栈指针向下移动(在内存地址上表现为减小),为变量开辟空间;当函数执行结束时,栈指针向上移动,这些局部变量所占用的空间会自动释放,无需程序员手动干预。
- 示例
void function() {int a = 10;int b[5];// 在这里进行其他操作
}
在这个函数中,a
是一个简单的int
类型局部变量,b
是一个包含 5 个int
元素的局部数组。当function
函数被调用时,编译器会在栈上为a
和b
分配足够的空间。当函数执行结束后,这些空间会自动被释放。
- 特点和限制:
- 优点是内存分配和释放速度非常快。因为栈的操作相对简单,只需要调整栈指针即可。
- 然而,栈的大小是有限制的。如果在栈上分配过多的内存,例如定义一个极大的局部数组,可能会导致栈溢出(Stack Overflow)错误。这种内存管理方式适用于函数内部临时使用的变量,对于需要在函数调用结束后仍然保留的数据,栈内存就不适用了。
🍃2.静态内存管理(全局和静态变量)
全局变量、静态局部变量和静态数据成员使用这种分配方式。
静态内存管理在程序启动时就已经将内存确定好
- 全局变量:
-
- 全局变量是在函数体外定义的变量。它们存储在全局 / 静态存储区。全局变量的作用域是从定义位置开始到整个文件结束(可以通过
extern
关键字扩展其作用域到其他文件)。其内存空间在程序启动时就已经分配好,并且在整个程序运行期间都存在。例如:
- 全局变量是在函数体外定义的变量。它们存储在全局 / 静态存储区。全局变量的作用域是从定义位置开始到整个文件结束(可以通过
int global_variable = 10;void function() {// 在这里可以访问和修改global_variableglobal_variable++;
}
- 静态变量:
- 静态变量分为静态局部变量和静态全局变量。静态局部变量是在函数内部定义的静态变量。它的特点是只在第一次进入函数时初始化,并且在函数调用结束后仍然保留其值。例如:
void function() {static int static_local_variable = 5;// 每次调用function函数,这个变量的值都会保留static_local_variable++;
}
- 静态全局变量的作用域仅限于定义它的文件,和全局变量一样,其内存空间在程序启动时分配,直到程序结束才释放。静态内存管理方式适合用于存储在整个程序运行期间都需要使用的数据,或者在函数多次调用之间需要保留状态的数据。但是,过多地使用全局变量可能会导致程序的可读性和可维护性变差,因为它们可以在程序的任何地方被修改。
🍃3.动态内存管理(堆内存)
- 使用
new
和delete
操作符(对于对象)或malloc
和free
函数(对于原始内存)进行分配和释放。 - 分配和释放由程序员显式控制。
- 存储在堆(heap)上。
动态内存管理较为重要,单独在下面拿出一个章节来讲解
四、C++ 动态内存管理方式
🍃1.new 和 delete 的用法
new 操作符
- 基本用法:
- new 用于在堆上分配内存。当你需要一个动态分配的单个对象时,可以使用
new
。例如,要在堆上分配一个int
类型的变量,可以写成int* p = new int;
。这会在堆中找到一块足够存储int
类型数据的空间,并返回该空间的指针,将其存储在p
中。你还可以在分配内存的同时进行初始化,如int* q = new int(5);
,这样就创建了一个值为 5 的int
变量。
- new 用于在堆上分配内存。当你需要一个动态分配的单个对象时,可以使用
#include <iostream>int main() {// 使用new分配一个int类型内存空间并赋值int* p = new int(5);std::cout << "通过new分配的int值为: " << *p << std::endl;// 释放内存delete p;return 0;
}
- 分配对象数组:
- 如果要分配一个对象数组,使用
new[]
操作符。例如,int* arr = new int[10];
会在堆上分配一个包含 10 个int
元素的数组,并返回数组的首地址。对于自定义类型的数组,例如class MyClass
,MyClass* myArr = new MyClass[5];
会调用默认构造函数来初始化数组中的每个元素。
- 如果要分配一个对象数组,使用
#include <iostream>class MyClass {
public:MyClass(int value) : data(value) {}int getData() const { return data; }private:int data;
};int main() {// 使用new[]分配包含3个MyClass对象的数组MyClass* myArr = new MyClass[3];// 简单赋值for (int i = 0; i < 3; ++i) {myArr[i].data = i + 1;}// 输出数组对象数据for (int i = 0; i < 3; ++i) {std::cout << "数组中第 " << i + 1 << " 个MyClass对象的数据为: " << myArr[i].getData() << std::endl;}// 释放数组内存delete[] myArr;return 0;
}
delete 操作符
- 基本用法:
- 与
new
相对应,delete
用于释放由new
分配的单个对象的内存。例如,对于前面通过new
分配的int* p
,在使用完后应该使用delete p;
来释放内存。如果忘记释放,就会导致内存泄漏。
- 与
#include <iostream>int main() {int* p = new int(5);std::cout << "通过new分配的int值为: " << *p << std::endl;// 释放内存,这里使用deletedelete p;return 0;
}
- 释放对象数组:
- 当释放由
new[]
分配的数组时,需要使用delete[]
操作符。例如,对于int* arr = new int[10];
,应该使用delete[] arr;
来正确释放数组所占用的内存。如果错误地使用delete
(而不是delete[]
)来释放数组,会导致程序出现未定义行为。
- 当释放由
#include <iostream>class MyClass {
public:MyClass(int value) : data(value) {}int getData() const { return data; }private:int data;
};int main() {MyClass* myArr = new MyClass[3];for (int i = 0; i < 3; ++i) {myArr[i].data = i + 1;}for (int i = 0; i < 3; ++i) {std::cout << "数组中第 " << i + 1 << " 个MyClass对象的数据为: " << myArr[i].getData() << std::endl;}// 释放数组内存,这里使用delete[]delete[] myArr;return 0;
}
🍃2. 使用 new 和 delete 的注意事项
- 内存泄漏:
- 最常见的问题是内存泄漏。如果在程序中使用
new
分配了内存,但没有使用delete
(或delete[]
)来释放,那么这块内存将一直被占用,直到程序结束。随着程序的运行,内存泄漏可能会导致系统内存耗尽。例如:
- 最常见的问题是内存泄漏。如果在程序中使用
void leakyFunction() {int* p = new int;// 忘记释放p指向的内存
}
- 多次释放:
- 另一个严重的问题是多次释放同一块内存。这也会导致程序出现未定义行为。例如:
void wrongDelete() {int* p = new int;delete p;// 错误地再次释放p指向的内存delete p;
}
- 指针初始化和赋值:
- 在使用
new
分配内存后,一定要确保指针正确地指向分配的内存。并且,在使用delete
后,最好将指针赋值为nullptr
,这样可以避免意外地使用已经释放的指针。例如:
- 在使用
void safeDelete() {int* p = new int;// 使用p...delete p;p = nullptr;
}
- 对象的构造和析构顺序:
- 当使用
new[]
分配对象数组时,会调用每个对象的构造函数来初始化。同样,在使用delete[]
释放数组时,会调用每个对象的析构函数。如果对象的构造和析构函数中有一些复杂的逻辑,比如资源的获取和释放,需要确保它们的正确执行顺序。
- 当使用
🍃3. new、delete 与 malloc、free 的区别
- 功能和语法:
- new/delete:
- new 和 delete 是 C++ 特有的操作符,它们除了分配和释放内存外,还会调用对象的构造函数和析构函数。new 的语法更简洁,对于对象的初始化也更加方便。例如,
new
可以直接在分配内存的同时进行初始化,而delete
在释放内存时会自动调用对象的析构函数。 new
和delete
是运算符,它们的操作是基于类型的,这使得代码更具类型安全性。
- new 和 delete 是 C++ 特有的操作符,它们除了分配和释放内存外,还会调用对象的构造函数和析构函数。new 的语法更简洁,对于对象的初始化也更加方便。例如,
- malloc/free:
- malloc 和 free 是 C 语言中的函数,用于在堆上分配和释放内存。malloc 只是简单地分配指定大小的内存块,例如
void* p = malloc(sizeof(int));
,它返回一个void*
类型的指针,需要手动进行类型转换。free 函数只是释放由 malloc 分配的内存,不会调用对象的构造函数或析构函数。
- malloc 和 free 是 C 语言中的函数,用于在堆上分配和释放内存。malloc 只是简单地分配指定大小的内存块,例如
- new/delete:
- 类型安全性:
- new/delete:
- 由于 new 和 delete 是基于类型的操作符,它们提供了更好的类型安全性。例如,
new
会根据要分配对象的类型来确定所需的内存大小,并且在编译时就可以检查类型相关的错误。如果试图用错误的类型指针来使用delete
,编译器可能会发出警告。
- 由于 new 和 delete 是基于类型的操作符,它们提供了更好的类型安全性。例如,
- malloc/free:
- malloc 和 free 的类型安全性相对较差。因为 malloc 返回的是一个
void*
指针,需要程序员手动进行类型转换。如果转换错误,可能会导致程序出现未定义行为,而且编译器很难在编译时发现这种错误。
- malloc 和 free 的类型安全性相对较差。因为 malloc 返回的是一个
- new/delete:
- 异常处理和错误返回:
- new/delete:
- 在 C++ 中,如果
new
分配内存失败(例如系统内存不足),会抛出一个bad_alloc
类型的异常。这使得程序可以通过异常处理机制来应对内存分配失败的情况。 delete
本身不会返回错误码,但是如果在错误的情况下使用(如释放未分配的内存或者多次释放同一块内存),会导致程序出现未定义行为。
- 在 C++ 中,如果
- malloc/free:
- malloc 在内存分配失败时返回
nullptr
,程序员需要检查这个返回值来确定是否分配成功。如果没有检查,使用nullptr
指针可能会导致程序崩溃。free 在释放内存时不会返回错误码,同样,如果错误地使用会导致未定义行为。
- malloc 在内存分配失败时返回
- new/delete:
五、内存管理注意事项
1.避免内存泄漏:
- 确保在不再需要使用动态分配的内存时,及时使用
delete
或delete[]
释放内存。 - 例如,在使用完一个动态分配的对象后,一定要释放它的内存:
MyClass* obj = new MyClass();
// 使用 obj
delete obj;
2.防止悬空指针:
- 悬空指针是指指向已被释放的内存的指针。避免在释放内存后继续使用该指针。
- 例如:
int* ptr = new int(10);
delete ptr;
// 这里 ptr 就成为了悬空指针,不能再使用它
3.注意内存分配失败:
new
操作可能会失败,返回nullptr
。在使用动态分配的内存之前,应该检查是否分配成功。- 例如:
int* ptr = new int(10);
if (ptr == nullptr) {std::cout << "Memory allocation failed." << std::endl;
} else {// 使用 ptrdelete ptr;
}
4.避免内存碎片:
- 频繁的动态内存分配和释放可能会导致内存碎片,降低内存的可用性。可以考虑使用内存池等技术来减少内存碎片。
总之,C++ 的内存管理需要程序员谨慎处理,以确保程序的正确性和性能。
理解 C++ 的内存区域划分、管理方式以及注意事项,对于编写高质量的 C++ 程序至关重要。
本文完。
我的博客即将同步至腾讯云开发者社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=nhl9kkcrfoft
相关文章:

【C++指南】C++内存管理 深度解析
💓 博客主页:倔强的石头的CSDN主页 📝Gitee主页:倔强的石头的gitee主页 ⏩ 文章专栏:《C指南》 期待您的关注 目录 引言 一、C 内存管理概述 二、C内存区域划分 三、C 内存管理方式 🍃1.自动内存管理…...

C++小碗菜之二:软件单元测试
“没有测试的代码重构不能称之为重构,它仅仅是垃圾代码的到处移动” ——Corey Haines 目录 前言 什么是单元测试? 单元测试的组成 单元测试的命名 单元测试的独立性 Google Test 单元测试的环境配置与使用 1. Ubuntu下安装 Google Test 2. 编写…...

PyCharm+Selenium+Pytest配置小记
1、下载ChromeDriver: Chrome130以后的Driver下载: Chrome for Testing availabilityhttps://googlechromelabs.github.io/chrome-for-testing/ (1)查看自己Crome浏览器的版本:设置-->关于 Chrome; &…...

摩尔线程 国产显卡 MUSA 并行编程 学习笔记-2024/12/04
Learning Roadmap: Section 1: Intro to Parallel Programming & MUSA Deep Learning Ecosystem(摩尔线程 国产显卡 MUSA 并行编程 学习笔记-2024/11/30-CSDN博客)UbuntuDriverToolkitcondapytorchtorch_musa环境安装(2024/11/24-Ubunt…...

【FAQ】HarmonyOS SDK 闭源开放能力 —Remote Communication Kit
1.问题描述: DynamicDnsRule有没有示例?这个地址是怎么解析出来 https://developer.huawei.com/consumer/cn/doc/harmonyos-references/remote-communication-rcp-0000001770911890#section8160554134811 解决方案: ‘DynamicDnsRule’&a…...

【日常记录-Mybatis】PageHelper导致语句截断
1. 简介 PageHelper是Mybatis-Plus中的一个插件,主要用于实现数据库的分页查询功能。其核心原理是将传入的页码和条数赋值给一个Page对象,并保存到本地线程ThreadLocal中,接下来,PageHelper会进入Mybatis的拦截器环节,…...

随时随地掌控数据:如何使用手机APP远程访问飞牛云NAS
💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…...

JVM 类加载器有哪些?双亲委派机制的作用是什么?如何自定义类加载器?
类加载器分类 大家好,我是码哥,可以叫我靓仔,《Redis 高手心法》畅销书作者。 先回顾下,在 Java 中,类的初始化分为几个阶段: 加载、链接(包括验证、准备和解析)和 初始化。 而 类加载器&#x…...

从基态到激发态再到里德伯态的双光子激发过程
铯原子(Cs)从基态6S1/2到激发态6P3/2再到里德伯态44D5/2的双光子激发过程, 并通过数值计算和图形化展示来研究不同失谐条件下的拉比频率、AC Stark位移差以及散射概率的变化 结果显示,在给定的实验参数下,拉比频率较低…...

Clickhouse 外部存储引擎
文章目录 外部存储引擎分类MySQL引擎PostgreSQL引擎MongoDB引擎JDBC引擎ODBC引擎Kafka引擎RabbitMQ引擎File引擎URL引擎HDFS引擎 外部存储引擎分类 引擎类型描述特点MySQL从 MySQL 数据库中读取数据用于与 MySQL 数据库共享数据,支持读取 MySQL 表中的数据 支持 SQ…...

eclipse怎么配置jdk路径?
在Eclipse中配置JDK路径是一个简单的步骤,以下是配置JDK路径的步骤: 打开Eclipse:启动Eclipse IDE。 访问首选项: 在Eclipse的菜单栏中,选择 Window > Preferences(对于Mac OS X用户,选择 E…...

【前端】JavaScript 中的创建对象模式要点
博客主页: [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: 前端 文章目录 💯前言💯对象属性值中的引号规则💯对象属性换行与尾随逗号的使用💯工厂模式:灵活高效的对象创建💯自定义构造函数:通过…...

GWAS分析先做后学
大家好,我是邓飞。 GWAS分析是生物信息和统计学的交叉学科,上可以学习编程,下可以学习统计。对于Linux系统,R语言,作图,统计学,机器学习等方向,都是一个极好的入门项目。生物信息如…...

【系统设计】高可用之缓存基础
缓存的缘起 使用缓存的主要原因包括提高系统性能、降低数据库负载、提升用户体验和保证系统可用性。 在计算机体系结构中,由于处理器和存储器的处理时间不匹配,在处理器和一个较大较慢的设备之间插入一个更小更快的存储设备(如高速缓存&a…...

《Java核心技术I》volatile字段
volatile字段 有多处理器的计算机能够暂时在寄存器或本地内存缓存中保存内存值,其结果是,运行在不同处理器上的线程可能看到同一个内存位置上有不同的值。编译器可以改变指令执行的顺序以使吞吐量更大化,编译器不会选择可能改变代码语义的顺…...

2024运维故障记 | 12/2 网易云音乐崩了
#运维故障记# 前两天看到网易云音乐崩了的新闻,回想了一下,今年从网易云音乐到支付宝、还有微软,近期就发生了好几起运维届的故障。 今年来不及计数了,先做个记录。 明年看看运维届的大故障会发生多少,什么原因&…...

架构设计读后——微服务
1 微服务历史 2005年:Dr. Peter Rodgers提出"Micro-Web-Services"概念2011年:一个软件架构工作组使用"microservice"来描述一中架构模式2012年;这个工作组正式使用"microservice"来代表这个架构2012年&#x…...

软考高级架构-9.4.4-双机热备技术 与 服务器集群技术
一、双机热备 1、特点: 软硬件结合:系统由两台服务器(主机和备机)、一个共享存储(通常为磁盘阵列柜)、以及双机热备软件(提供心跳检测、故障转移和资源管理功能的核心软件)组成。 …...

聊聊前端工程化
深度解析前端工程化 近年来,随着前端技术的快速迭代和项目复杂度的增加,前端开发已经从简单的页面搭建演变为专业的工程化体系。前端工程化通过工具链、标准化和流程化手段,不仅提高了开发效率,也大幅提升了项目的可维护性和协…...

“放弃Redis Desktop Manager使用Redis Insight”:日常使用教程(Redis可视化工具)
文章目录 更新Redis Insight连接页面基础解释自动更新key汉化暂时没有找到方法, Redis Desktop Manager在连接上右键在数据库上右键在key上右键1、添加连接2、key过期时间 参考文章 更新 (TωT)ノ~~~ βyё βyё~ 现在在维护另一…...

mmdection配置-yolo转coco
基础配置看我的mmsegmentation。 也可以参考b站 :https://www.bilibili.com/video/BV1xA4m1c7H8/?vd_source701421543dabde010814d3f9ea6917f6#reply248829735200 这里面最大的坑就是配置coco数据集。我一般是用yolo,这个yolo转coco格式很难搞定&#…...

聚合支付系统/官方个人免签系统/三方支付系统稳定安全高并发 附教程
聚合支付系统/官方个人免签系统/三方支付系统稳定安全高并发 附教程 系统采用FastAdmin框架独立全新开发,安全稳定,系统支持代理、商户、码商等业务逻辑。 针对最近一些JD,TB等业务定制,子账号业务逻辑API 非常详细,方便内置…...

力扣67. 二进制求和
给你两个二进制字符串 a 和 b ,以二进制字符串的形式返回它们的和。 示例 1: 输入:a "11", b "1" 输出:"100" 示例 2: 输入:a "1010", b "1011" 输出&#…...

网络安全中的 SOC 是什么?
当今世界,网络威胁日益增多,确保网络安全已成为各种规模企业的首要任务。网络安全讨论中经常出现的一个术语是 SOC,即安全运营中心的缩写。但网络安全中的 SOC 是什么呢? SOC在防御网络威胁、管理安全事件和全天候监控系统方面发…...

16、鸿蒙学习——Visibility与(if...else)该如何选择
在鸿蒙中我们如果要控制一个组件的显示与隐藏可以设置组件的Visibility属性,也可使用(if...else)条件控制,具体我们该选择哪个?二者有什么区别呢? 1、Visibility 名称描述Hidden隐藏,但参与布局…...

PH热榜 | 2024-12-05
1. Oopsie 标语:用AI和会话回放调试Flutter和React Native应用 介绍:Zipy推出的Oopsie是一款你唯一需要的AI赋能移动端调试工具,它能提供▶️会话回放、🤖错误监控、💡AI生成的概要分析,以及🔥…...

Qt Chart 模块化封装曲线图
一 版本说明 二 完成示例 此文章包含:曲线轴设置,曲线切换,单条曲线显示,坐标轴。。。 三 曲线图UI创建 在UI界面拖放一个QWidget,然后在 Widget里面放一个 graphicsView 四 代码介绍 1 头文件 #include <QString> #include <QTimer> #include <QMessa…...

【AI系统】MobileFormer
MobileFormer 在本文中,将介绍一种新的网络-MobileFormer,它实现了 Transformer 全局特征与 CNN 局部特征的融合,在较低的成本内,创造一个高效的网络。通过本节,让大家去了解如何将 CNN 与 Transformer 更好的结合起来…...

python数据分析之爬虫基础:解析
目录 1、xpath 1.1、xpath的安装以及lxml的安装 1.2、xpath的基本使用 1.3、xpath基本语法 2、JsonPath 2.1、jsonpath的安装 2.2、jsonpath的使用 2.3、jsonpath的基础语法 3、BeautifulSoup 3.1、bs4安装及创建 3.2、beautifulsoup的使用 3.3、beautifulsoup基本语…...

uniapp中导入uview或者uview plus
关于SCSS uview-plus依赖SCSS,您必须要安装此插件,否则无法正常运行。 如果您的项目是由HBuilder X创建的,相信已经安装scss插件,如果没有,请在HX菜单的 工具->插件安装中找到"scss/sass编译"插件进行安…...