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

【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++ 提供的内存管理工具,提高程序的质量和性能。​


相关文章:

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

目录 一、C 内存的基本概念​ 1.1 内存的物理与逻辑结构​ 1.2 C 程序的内存区域划分​ 二、栈内存分配​ 2.1 栈内存的特点​ 2.2 栈内存分配示例​ 三、堆内存分配​ 3.1 new和delete操作符​ 4.2 内存泄漏与悬空指针问题​ 4.3 new和delete的重载​ 四、智能指针…...

vulnyx Blogger writeup

信息收集 arp-scan nmap 获取userFlag 上web看看 一个默认的页面&#xff0c;gobuster扫一下目录 可以看到扫出的目录中得到了一个有价值的目录/wordpress&#xff0c;说明目标所使用的cms是wordpress&#xff0c;访问http://192.168.43.213/wordpress/然后查看源码能看到 这…...

基于IDIG-GAN的小样本电机轴承故障诊断

目录 🔍 核心问题 一、IDIG-GAN模型原理 1. 整体架构 2. 核心创新点 (1) ​梯度归一化(Gradient Normalization)​​ (2) ​判别器梯度间隙正则化(Discriminator Gradient Gap Regularization)​​ (3) ​自注意力机制(Self-Attention)​​ 3. 完整损失函数 二…...

【JVM面试篇】高频八股汇总——类加载和类加载器

目录 1. 讲一下类加载过程&#xff1f; 2. Java创建对象的过程&#xff1f; 3. 对象的生命周期&#xff1f; 4. 类加载器有哪些&#xff1f; 5. 双亲委派模型的作用&#xff08;好处&#xff09;&#xff1f; 6. 讲一下类的加载和双亲委派原则&#xff1f; 7. 双亲委派模…...

push [特殊字符] present

push &#x1f19a; present 前言present和dismiss特点代码演示 push和pop特点代码演示 前言 在 iOS 开发中&#xff0c;push 和 present 是两种不同的视图控制器切换方式&#xff0c;它们有着显著的区别。 present和dismiss 特点 在当前控制器上方新建视图层级需要手动调用…...

虚拟电厂发展三大趋势:市场化、技术主导、车网互联

市场化&#xff1a;从政策驱动到多元盈利 政策全面赋能 2025年4月&#xff0c;国家发改委、能源局发布《关于加快推进虚拟电厂发展的指导意见》&#xff0c;首次明确虚拟电厂为“独立市场主体”&#xff0c;提出硬性目标&#xff1a;2027年全国调节能力≥2000万千瓦&#xff0…...

LLMs 系列实操科普(1)

写在前面&#xff1a; 本期内容我们继续 Andrej Karpathy 的《How I use LLMs》讲座内容&#xff0c;原视频时长 ~130 分钟&#xff0c;以实操演示主流的一些 LLMs 的使用&#xff0c;由于涉及到实操&#xff0c;实际上并不适合以文字整理&#xff0c;但还是决定尽量整理一份笔…...

RabbitMQ入门4.1.0版本(基于java、SpringBoot操作)

RabbitMQ 一、RabbitMQ概述 RabbitMQ RabbitMQ最初由LShift和CohesiveFT于2007年开发&#xff0c;后来由Pivotal Software Inc.&#xff08;现为VMware子公司&#xff09;接管。RabbitMQ 是一个开源的消息代理和队列服务器&#xff0c;用 Erlang 语言编写。广泛应用于各种分布…...

DingDing机器人群消息推送

文章目录 1 新建机器人2 API文档说明3 代码编写 1 新建机器人 点击群设置 下滑到群管理的机器人&#xff0c;点击进入 添加机器人 选择自定义Webhook服务 点击添加 设置安全设置&#xff0c;详见说明文档 成功后&#xff0c;记录Webhook 2 API文档说明 点击设置说明 查看自…...

[免费]微信小程序问卷调查系统(SpringBoot后端+Vue管理端)【论文+源码+SQL脚本】

大家好&#xff0c;我是java1234_小锋老师&#xff0c;看到一个不错的微信小程序问卷调查系统(SpringBoot后端Vue管理端)【论文源码SQL脚本】&#xff0c;分享下哈。 项目视频演示 【免费】微信小程序问卷调查系统(SpringBoot后端Vue管理端) Java毕业设计_哔哩哔哩_bilibili 项…...

Selenium常用函数介绍

目录 一&#xff0c;元素定位 1.1 cssSeector 1.2 xpath 二&#xff0c;操作测试对象 三&#xff0c;窗口 3.1 案例 3.2 窗口切换 3.3 窗口大小 3.4 屏幕截图 3.5 关闭窗口 四&#xff0c;弹窗 五&#xff0c;等待 六&#xff0c;导航 七&#xff0c;文件上传 …...

在Mathematica中实现Newton-Raphson迭代的收敛时间算法(一般三次多项式)

考察一般的三次多项式&#xff0c;以r为参数&#xff1a; p[z_, r_] : z^3 (r - 1) z - r; roots[r_] : z /. Solve[p[z, r] 0, z]&#xff1b; 此多项式的根为&#xff1a; 尽管看起来这个多项式是特殊的&#xff0c;其实一般的三次多项式都是可以通过线性变换化为这个形式…...

华为OD机考-机房布局

import java.util.*;public class DemoTest5 {public static void main(String[] args) {Scanner in new Scanner(System.in);// 注意 hasNext 和 hasNextLine 的区别while (in.hasNextLine()) { // 注意 while 处理多个 caseSystem.out.println(solve(in.nextLine()));}}priv…...

莫兰迪高级灰总结计划简约商务通用PPT模版

莫兰迪高级灰总结计划简约商务通用PPT模版&#xff0c;莫兰迪调色板清新简约工作汇报PPT模版&#xff0c;莫兰迪时尚风极简设计PPT模版&#xff0c;大学生毕业论文答辩PPT模版&#xff0c;莫兰迪配色总结计划简约商务通用PPT模版&#xff0c;莫兰迪商务汇报PPT模版&#xff0c;…...

力扣热题100 k个一组反转链表题解

题目: 代码: func reverseKGroup(head *ListNode, k int) *ListNode {cur : headfor i : 0; i < k; i {if cur nil {return head}cur cur.Next}newHead : reverse(head, cur)head.Next reverseKGroup(cur, k)return newHead }func reverse(start, end *ListNode) *ListN…...

JS手写代码篇----使用Promise封装AJAX请求

15、使用Promise封装AJAX请求 promise就有reject和resolve了&#xff0c;就不必写成功和失败的回调函数了 const BASEURL ./手写ajax/test.jsonfunction promiseAjax() {return new Promise((resolve, reject) > {const xhr new XMLHttpRequest();xhr.open("get&quo…...

08. C#入门系列【类的基本概念】:开启编程世界的奇妙冒险

C#入门系列【类的基本概念】&#xff1a;开启编程世界的奇妙冒险 嘿&#xff0c;各位编程小白探险家&#xff01;欢迎来到 C# 的奇幻大陆&#xff01;今天咱们要深入探索这片大陆上至关重要的 “建筑”—— 类&#xff01;别害怕&#xff0c;跟着我&#xff0c;保准让你轻松搞…...

如何更改默认 Crontab 编辑器 ?

在 Linux 领域中&#xff0c;crontab 是您可能经常遇到的一个术语。这个实用程序在类 unix 操作系统上可用&#xff0c;用于调度在预定义时间和间隔自动执行的任务。这对管理员和高级用户非常有益&#xff0c;允许他们自动执行各种系统任务。 编辑 Crontab 文件通常使用文本编…...

2025年渗透测试面试题总结-腾讯[实习]科恩实验室-安全工程师(题目+回答)

安全领域各种资源&#xff0c;学习文档&#xff0c;以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具&#xff0c;欢迎关注。 目录 腾讯[实习]科恩实验室-安全工程师 一、网络与协议 1. TCP三次握手 2. SYN扫描原理 3. HTTPS证书机制 二…...

uniapp手机号一键登录保姆级教程(包含前端和后端)

目录 前置条件创建uniapp项目并关联uniClound云空间开启一键登录模块并开通一键登录服务编写云函数并上传部署获取手机号流程(第一种) 前端直接调用云函数获取手机号&#xff08;第三种&#xff09;后台调用云函数获取手机号 错误码常见问题 前置条件 手机安装有sim卡手机开启…...

JavaScript 数据类型详解

JavaScript 数据类型详解 JavaScript 数据类型分为 原始类型&#xff08;Primitive&#xff09; 和 对象类型&#xff08;Object&#xff09; 两大类&#xff0c;共 8 种&#xff08;ES11&#xff09;&#xff1a; 一、原始类型&#xff08;7种&#xff09; 1. undefined 定…...

【Linux】Linux 系统默认的目录及作用说明

博主介绍&#xff1a;✌全网粉丝23W&#xff0c;CSDN博客专家、Java领域优质创作者&#xff0c;掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域✌ 技术范围&#xff1a;SpringBoot、SpringCloud、Vue、SSM、HTML、Nodejs、Python、MySQL、PostgreSQL、大数据、物…...

RSS 2025|从说明书学习复杂机器人操作任务:NUS邵林团队提出全新机器人装配技能学习框架Manual2Skill

视觉语言模型&#xff08;Vision-Language Models, VLMs&#xff09;&#xff0c;为真实环境中的机器人操作任务提供了极具潜力的解决方案。 尽管 VLMs 取得了显著进展&#xff0c;机器人仍难以胜任复杂的长时程任务&#xff08;如家具装配&#xff09;&#xff0c;主要受限于人…...

搭建DNS域名解析服务器(正向解析资源文件)

正向解析资源文件 1&#xff09;准备工作 服务端及客户端都关闭安全软件 [rootlocalhost ~]# systemctl stop firewalld [rootlocalhost ~]# setenforce 0 2&#xff09;服务端安装软件&#xff1a;bind 1.配置yum源 [rootlocalhost ~]# cat /etc/yum.repos.d/base.repo [Base…...

【Nginx】使用 Nginx+Lua 实现基于 IP 的访问频率限制

使用 NginxLua 实现基于 IP 的访问频率限制 在高并发场景下&#xff0c;限制某个 IP 的访问频率是非常重要的&#xff0c;可以有效防止恶意攻击或错误配置导致的服务宕机。以下是一个详细的实现方案&#xff0c;使用 Nginx 和 Lua 脚本结合 Redis 来实现基于 IP 的访问频率限制…...

腾讯云V3签名

想要接入腾讯云的Api&#xff0c;必然先按其文档计算出所要求的签名。 之前也调用过腾讯云的接口&#xff0c;但总是卡在签名这一步&#xff0c;最后放弃选择SDK&#xff0c;这次终于自己代码实现。 可能腾讯云翻新了接口文档&#xff0c;现在阅读起来&#xff0c;清晰了很多&…...

MySQL JOIN 表过多的优化思路

当 MySQL 查询涉及大量表 JOIN 时&#xff0c;性能会显著下降。以下是优化思路和简易实现方法&#xff1a; 一、核心优化思路 减少 JOIN 数量 数据冗余&#xff1a;添加必要的冗余字段&#xff08;如订单表直接存储用户名&#xff09;合并表&#xff1a;将频繁关联的小表合并成…...

mac 安装homebrew (nvm 及git)

mac 安装nvm 及git 万恶之源 mac 安装这些东西离不开Xcode。及homebrew 一、先说安装git步骤 通用&#xff1a; 方法一&#xff1a;使用 Homebrew 安装 Git&#xff08;推荐&#xff09; 步骤如下&#xff1a;打开终端&#xff08;Terminal.app&#xff09; 1.安装 Homebrew…...

C++课设:简易日历程序(支持传统节假日 + 二十四节气 + 个人纪念日管理)

名人说:路漫漫其修远兮,吾将上下而求索。—— 屈原《离骚》 创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 专栏介绍:《编程项目实战》 目录 一、为什么要开发一个日历程序?1. 深入理解时间算法2. 练习面向对象设计3. 学习数据结构应用二、核心算法深度解析…...

【C++进阶篇】智能指针

C内存管理终极指南&#xff1a;智能指针从入门到源码剖析 一. 智能指针1.1 auto_ptr1.2 unique_ptr1.3 shared_ptr1.4 make_shared 二. 原理三. shared_ptr循环引用问题三. 线程安全问题四. 内存泄漏4.1 什么是内存泄漏4.2 危害4.3 避免内存泄漏 五. 最后 一. 智能指针 智能指…...