【C++特殊工具与技术】优化内存分配(一):C++中的内存分配

目录

一、C++ 内存的基本概念​

1.1 内存的物理与逻辑结构​

1.2 C++ 程序的内存区域划分​

二、栈内存分配​

2.1 栈内存的特点​

2.2 栈内存分配示例​

三、堆内存分配​

3.1 new和delete操作符​

4.2 内存泄漏与悬空指针问题​

4.3 new和delete的重载​

四、智能指针与动态内存管理​

4.1 智能指针的概念​

4.2 std::unique_ptr​

4.3 std::shared_ptr​

4.4 std::weak_ptr​

五、总结​


在 C++ 编程中,内存管理是一个至关重要的环节。合理的内存分配和管理不仅能提高程序的性能,还能避免诸如内存泄漏、悬空指针等严重问题。C++ 提供了多种内存分配方式,从基础的栈内存分配到灵活的堆内存分配,每种方式都有其特点和适用场景。

一、C++ 内存的基本概念​

1.1 内存的物理与逻辑结构​

在计算机系统中,物理内存是实际的硬件存储设备,用于存储程序运行时的数据和指令。而逻辑内存则是操作系统为每个进程提供的一个抽象的内存空间视图,它使得每个进程都认为自己拥有整个系统内存。操作系统通过内存管理单元(MMU)将逻辑地址转换为物理地址,实现内存的高效管理和保护。​

1.2 C++ 程序的内存区域划分​

C++ 程序在运行时,其内存空间通常被划分为以下几个区域:​

  • 栈(Stack):栈是一块连续的内存区域,由编译器自动管理。主要用于存储局部变量、函数参数、返回地址等。栈的分配和释放速度非常快,遵循后进先出(LIFO)的原则。​
  • 堆(Heap):堆是一块不连续的内存区域,用于动态内存分配。程序员通过new和delete操作符在堆上分配和释放内存。堆内存的管理相对复杂,容易出现内存泄漏等问题。​
  • 全局 / 静态存储区:用于存储全局变量和静态变量。该区域在程序启动时分配,程序结束时释放。全局变量和静态变量根据初始化情况,又分为初始化的全局 / 静态存储区和未初始化的全局 / 静态存储区(BSS 段) 。​
  • 常量存储区:用于存储常量,如字符串常量。常量存储区的内容在程序运行期间是只读的。​

可以用下图来直观展示 C++ 程序的内存区域划分:

嵌入式C语言:内存管理_嵌入式内存管理-CSDN博客 

二、栈内存分配​

2.1 栈内存的特点​

栈内存具有以下特点:​

  • 自动管理:栈内存的分配和释放由编译器自动完成,无需程序员手动干预。​
  • 速度快:由于栈的操作遵循后进先出原则,并且是在连续的内存区域进行操作,所以栈内存的分配和释放速度非常快。​
  • 大小有限:栈的大小在程序运行前通常是固定的,不同的操作系统和编译器对栈的大小限制不同。如果函数调用层级过深,或者局部变量占用空间过大,可能会导致栈溢出。​

2.2 栈内存分配示例​

下面通过一个简单的 C++ 代码示例来展示栈内存的分配和使用: 

#include <iostream>void function() {int localVar = 10;  // 局部变量 localVar 在栈上分配内存std::cout << "Local variable in function: " << localVar << std::endl;
}int main() {int mainVar = 20;  // 局部变量 mainVar 在栈上分配内存std::cout << "Local variable in main: " << mainVar << std::endl;function();return 0;
}

mainVar和localVar都是局部变量,它们在函数调用时在栈上分配内存,函数结束时,栈内存会自动释放。​

三、堆内存分配​

3.1 new和delete操作符​

在 C++ 中,使用new操作符在堆上分配内存,使用delete操作符释放堆内存。new操作符返回一个指向分配内存的指针,delete操作符用于释放new分配的内存。​

①基本数据类型的堆内存分配 

#include <iostream>int main() {int* ptr = new int;  // 在堆上分配一个 int 类型的内存空间*ptr = 10;  // 向分配的内存空间写入数据std::cout << "Value in heap memory: " << *ptr << std::endl;delete ptr;  // 释放堆内存return 0;
}

②数组的堆内存分配 

#include <iostream>int main() {int* arr = new int[5];  // 在堆上分配一个包含 5 个 int 元素的数组for (int i = 0; i < 5; ++i) {arr[i] = i;}for (int i = 0; i < 5; ++i) {std::cout << "arr[" << i << "]: " << arr[i] << std::endl;}delete[] arr;  // 释放堆上的数组内存return 0;
}

需要注意的是,在释放数组内存时,必须使用delete[],否则可能会导致内存泄漏或程序崩溃。​

4.2 内存泄漏与悬空指针问题​

①内存泄漏:当使用new分配的内存没有通过delete释放时,就会发生内存泄漏。随着程序的运行,内存泄漏会导致可用内存逐渐减少,最终可能导致程序性能下降甚至崩溃。

#include <iostream>void memoryLeak() {int* ptr = new int;// 没有调用 delete ptr,导致内存泄漏
}int main() {memoryLeak();return 0;
}

②悬空指针:当通过delete释放了堆内存后,如果没有将指针设置为nullptr,该指针就会成为悬空指针。使用悬空指针进行解引用操作会导致未定义行为。 

#include <iostream>int main() {int* ptr = new int;*ptr = 10;delete ptr;// 此时 ptr 成为悬空指针std::cout << *ptr << std::endl;  // 未定义行为return 0;
}

为了避免悬空指针问题,可以在释放内存后将指针设置为nullptr:

#include <iostream>int main() {int* ptr = new int;*ptr = 10;delete ptr;ptr = nullptr;  // 将指针设置为 nullptrreturn 0;
}

4.3 new和delete的重载​

在 C++ 中,可以重载new和delete操作符,以实现自定义的内存分配策略。例如,可以实现内存池来提高内存分配的效率,减少内存碎片。 

#include <iostream>
#include <cstdlib>class MemoryPool {
private:static const size_t POOL_SIZE = 1024;  // 内存池大小char* pool;size_t current;public:MemoryPool() : pool(static_cast<char*>(std::malloc(POOL_SIZE))), current(0) {}~MemoryPool() { std::free(pool); }void* allocate(size_t size) {if (POOL_SIZE - current < size) {return std::malloc(size);  // 内存池不足时,使用标准 malloc}void* result = pool + current;current += size;return result;}void deallocate(void* ptr) {// 简单实现,不支持真正的释放回内存池,仅标记为可分配if (ptr >= pool && ptr < pool + POOL_SIZE) {// 这里可以添加更复杂的标记逻辑} else {std::free(ptr);}}
};MemoryPool globalPool;void* operator new(size_t size) {return globalPool.allocate(size);
}void operator delete(void* ptr) noexcept {globalPool.deallocate(ptr);
}class MyClass {
public:int data;MyClass() : data(0) {}
};int main() {MyClass* obj = new MyClass;obj->data = 10;std::cout << "Data in MyClass: " << obj->data << std::endl;delete obj;return 0;
}

通过重载new和delete操作符,实现了一个简单的内存池。当分配内存时,优先从内存池中获取,如果内存池不足,则使用标准的malloc函数。释放内存时,对于从内存池中分配的内存,简单标记为可分配(实际应用中可实现更复杂的回收逻辑),对于使用malloc分配的内存,则使用free释放。​

四、智能指针与动态内存管理​

4.1 智能指针的概念​

智能指针是 C++ 标准库提供的一种用于自动管理动态内存的类模板。它通过封装原始指针,并在适当的时候自动释放所指向的内存,从而避免了手动管理内存时容易出现的内存泄漏和悬空指针问题。​

4.2 std::unique_ptr​

std::unique_ptr是一种独占所有权的智能指针,它不支持拷贝构造和赋值操作,确保每个std::unique_ptr实例都唯一地拥有所指向的对象。当std::unique_ptr对象被销毁时,它所指向的内存会自动释放。 

#include <iostream>
#include <memory>class MyClass {
public:MyClass() { std::cout << "MyClass constructor" << std::endl; }~MyClass() { std::cout << "MyClass destructor" << std::endl; }
};int main() {std::unique_ptr<MyClass> ptr(new MyClass);  // 创建 std::unique_ptr// ptr 离开作用域时,MyClass 对象的内存会自动释放return 0;
}

std::unique_ptr还提供了release和reset等成员函数,用于转移所有权和释放当前指向的对象。​

4.3 std::shared_ptr​

std::shared_ptr是一种共享所有权的智能指针,多个std::shared_ptr可以指向同一个对象,通过引用计数来管理对象的生命周期。当最后一个指向对象的std::shared_ptr被销毁时,对象的内存会自动释放。 

#include <iostream>
#include <memory>class MyClass {
public:MyClass() { std::cout << "MyClass constructor" << std::endl; }~MyClass() { std::cout << "MyClass destructor" << std::endl; }
};int main() {std::shared_ptr<MyClass> ptr1(new MyClass);  // 创建 std::shared_ptrstd::shared_ptr<MyClass> ptr2 = ptr1;  // 共享所有权,引用计数加 1// 当 ptr1 和 ptr2 都离开作用域时,MyClass 对象的内存会自动释放return 0;
}

std::shared_ptr还提供了use_count等成员函数,用于获取当前对象的引用计数。​

4.4 std::weak_ptr​

std::weak_ptr是一种弱引用的智能指针,它不影响对象的生命周期,主要用于解决std::shared_ptr循环引用的问题。std::weak_ptr不能直接解引用,需要先通过lock函数将其转换为std::shared_ptr。 

#include <iostream>
#include <memory>class B;class A {
public:std::weak_ptr<B> ptrB;~A() { std::cout << "A destructor" << std::endl; }
};class B {
public:std::weak_ptr<A> ptrA;~B() { std::cout << "B destructor" << std::endl; }
};int main() {std::shared_ptr<A> a(new A);std::shared_ptr<B> b(new B);a->ptrB = b;b->ptrA = a;// 当 a 和 b 离开作用域时,对象 A 和 B 的内存会正常释放return 0;
}

通过std::weak_ptr打破了A和B之间的循环引用,避免了内存泄漏。​

五、总结​

本文详细介绍了 C++ 中的内存分配相关知识,包括栈内存和堆内存的分配方式、new和delete操作符的使用、内存泄漏和悬空指针问题,以及智能指针在动态内存管理中的应用。合理选择和使用内存分配方式,正确处理内存管理问题,对于编写高效、稳定的 C++ 程序至关重要。在实际编程中,应根据具体需求选择合适的内存管理策略,充分利用 C++ 提供的内存管理工具,提高程序的质量和性能。​


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/bicheng/83886.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

DeepSeek 赋能智慧能源:微电网优化调度的智能革新路径

目录 一、智慧能源微电网优化调度概述1.1 智慧能源微电网概念1.2 优化调度的重要性1.3 目前面临的挑战 二、DeepSeek 技术探秘2.1 DeepSeek 技术原理2.2 DeepSeek 独特优势2.3 DeepSeek 在 AI 领域地位 三、DeepSeek 在微电网优化调度中的应用剖析3.1 数据处理与分析3.2 预测与…

Redis配合唯一序列号实现接口幂等性方案

1.原理 可以在客户端每次请求服务端的时候&#xff0c;客户端请求中携带一个短时间内唯一不重复的序列号来确保其唯一性&#xff0c;这个序列号常见的几种形式有&#xff1a;基于时间戳、用户ID和随机数的组合&#xff1b;基于请求的来源与客户端生成的唯一序列号组合 2.方案…

代码安全规范1.1

命令注入是指应用程序执行命令的字符串或字符串的一部分来源于不可信赖的数据源&#xff0c;程序没有对这 些不可信赖的数据进行验证、过滤&#xff0c;导致程序执行恶意命令的一种攻击方式。 例 1 &#xff1a;以下代码通过 Runtime.exec() 方法调用 Windows 的 dir 命…

Jenkins实现自动化部署Springboot项目到Docker容器(Jenkinsfile)

Jenkins实现自动化部署Springboot项目到Docker容器 引言:为什么需要自动化部署? 在软件开发中,频繁的手动部署既耗时又容易出错。通过 Docker + Jenkins + Git 的组合,您可以实现: ✅ 一键部署:代码推送后自动构建和部署🐳 环境一致性:Docker 确保开发、测试、生产环…

第二届智慧教育与计算机技术国际学术会议(IECT 2025)

在数字化浪潮中&#xff0c;智慧教育与计算机技术的深度融合正重构教育生态。智能教学系统打破传统课堂的单向灌输模式&#xff0c;通过机器学习分析学习数据&#xff0c;为学生生成个性化学习路径&#xff0c;推动被动接受向主动探索转型。这对教育体系提出核心诉求&#xff1…

驱控边界在哪里?知名舵机品牌伟创动力CNTE2025展带来答案

2025年6月12日&#xff0c;北京国防科技装备展将再度启幕。作为微型驱控领域的代表性厂商&#xff0c;伟创动力&#xff08;Kpower&#xff09;将带来覆盖舵机、减速齿轮箱、无刷电机及一体化驱控模组在内的全系解决方案&#xff0c;舵机产品回应一个至关重要的技术命题——“国…

Day46 Python打卡训练营

知识点回顾&#xff1a; 1. 不同CNN层的特征图&#xff1a;不同通道的特征图 2. 什么是注意力&#xff1a;注意力家族&#xff0c;类似于动物园&#xff0c;都是不同的模块&#xff0c;好不好试了才知道。 3. 通道注意力&#xff1a;模型的定义和插入的位置 4. 通道注意力后…

专业级PDF转CAD解决方案

PDF 文件因其出色的便携性和稳定性&#xff0c;已成为许多用户的首选格式。但在涉及图像编辑或精细调整时&#xff0c;CAD 文件显然更具优势。 这款 CAD 图纸转换工具&#xff0c;界面清爽、操作直观&#xff0c;是处理图纸文件的理想助手。 它不仅支持不同版本 CAD 文件之间…

PDF文件如何转换格式?简单教程来了

PDF 格式以其高兼容性和稳定性被广泛使用&#xff0c;但有时为了便于编辑或满足特定软件的要求&#xff0c;我们需要将其转换为其他格式&#xff0c;如 Word、Excel、图片等。那如何将PDF转换成其他格式文件呢&#xff1f;其实方法很简单&#xff0c;不清楚的小伙伴一起来看看吧…

三十四、面向对象底层逻辑-SpringMVC九大组件之FlashMapManager接口设计哲学

在构建符合 RESTful 原则或追求用户体验流畅性的 Web 应用时&#xff0c;“重定向后刷新”&#xff08;PRG - Post/Redirect/Get&#xff09;模式是避免表单重复提交、实现页面无刷新跳转的黄金法则。然而&#xff0c;重定向&#xff08;REDIRECT:&#xff09;的本质是客户端发…

android手势创建及识别保姆级教程

手势交互&#xff0c;简单来说&#xff0c;就是通过手指在屏幕上的滑动、点击、缩放等动作与设备沟通的方式&#xff0c;早已成为现代移动设备用户体验的核心支柱。想想看&#xff0c;无论是日常刷短视频时的上下滑动&#xff0c;还是地图导航时的双指缩放&#xff0c;甚至是游…

Python | Windows11通过离线方式安装pyserial

导言 因公司网络访问的限制&#xff0c;没办法使用pip install pyserial轻松地安装pyserial库。 打开网页&#xff1a;https://pypi.org/project/pyserial/#files 下载.whl cmd命令行 如下是命令行指令&#xff1a; pip install .\pyserial-3.5-py2.py3-none-any.whlpython …

【nano与Vim】常用命令

使用nano编辑器 保存文件 &#xff1a; 按下CtrlO组合键&#xff0c;然后按Enter键确认文件名。 退出编辑器 &#xff1a; 按下CtrlX组合键。 使用vi或vim编辑器 保存文件 &#xff1a; 按Esc键退出插入模式&#xff0c;然后输入:w并按Enter键保存文件。 退出编辑器 &#xf…

(Python网络爬虫);抓取B站404页面小漫画

目录 一. 分析网页 二. 准备工作 三. 实现爬虫 1. 抓取工作 2. 分析工作 3. 拼接主函数&运行结果 四. 完整代码清单 1.多线程版本spider.py&#xff1a; 2.异步版本async_spider.py&#xff1a; 经常逛B站的同志们可能知道&#xff0c;B站的404页面做得别具匠心&…

实战设计模式之模板方法模式

概述 模板方法模式定义了一个操作中的算法骨架&#xff0c;并将某些步骤延迟到子类中实现。模板方法使得子类可以在不改变算法结构的前提下&#xff0c;重新定义算法中的某些步骤。简单来说&#xff0c;就是在一个方法中定义了要执行的步骤顺序或算法框架&#xff0c;但允许子类…

ROS1: 使用rosbag的方式将点云topic保存为pcd文件

ROS1: 使用rosbag的方式将点云topic保存为pcd文件。 分为两步&#xff1a;步骤1&#xff1a;通过rosbag录制点云 &#xff0c;步骤2&#xff1a;通过ros1将rosbag保存为点云pcd文件。 ------------------------ 步骤一&#xff1a;指令示例如下&#xff1a; # topic 名称&a…

MySQL 高级学习篇

一、连结&#xff08;Join&#xff09; 1.1 概念 联结&#xff08;Join&#xff09;操作用于将多个表中的列组合在一起&#xff0c;形成一个新的查询结果集。它允许我们从多个表中提取数据&#xff0c;并基于表之间的关系进行查询。 1.2 类型 1. 内联结&#xff08;INNER J…

clickhouse 学习总结

在 ClickHouse 中&#xff0c;配置文件通常位于 /etc/clickhouse 目录下。这个目录包含了多个配置文件&#xff0c;用于控制 ClickHouse 的各种服务&#xff08;如服务器、用户、远程服务等&#xff09;的配置。 数据存储目录/var/lib/clickhouse 配置 文件目录 /etc/clickho…

理解JavaScript中map和parseInt的陷阱:一个常见的面试题解析

前言 在JavaScript面试中&#xff0c;map和parseInt的组合常常被用作考察候选人对这两个方法理解深度的题目。让我们通过一个简单的例子来深入探讨其中的原理。 问题现象 [1, 2, 3].map(parseInt) // 输出结果是什么&#xff1f;很多人可能会预期输出[1, 2, 3]&#xff0c;但…

字符串 金额转换

package heima.Test09;import java.util.Scanner;public class Money {public static void main(String[] args) {//1。键盘录入一个金额Scanner sc new Scanner(System.in);//请输入一个数据String result "";int money;while (true) {System.out.println("请…