【C++指南】C++内存管理 深度解析

news2025/2/26 14:15:19

           💓 博客主页:倔强的石头的CSDN主页 

           📝Gitee主页:倔强的石头的gitee主页

            ⏩ 文章专栏:《C++指南》

                                  期待您的关注

47f09392526c71b5885ec838a3ea7ffe.gif

目录

引言

一、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语言的内存区域划分是相同的,都包括栈区、堆区、全局/静态区、常量区和代码区。这些区域在功能、生命周期、管理方式和特性上都有所不同,共同支持着程序的正常运行。
ad3a88d2354747d5b2fe1d4ed0af9746.png

相关细节可以阅读我的上一篇文章:

【C语言指南】C语言内存管理 深度解析_c语言内存映射-CSDN博客

三、C++ 内存管理方式

🍃1.自动内存管理(栈内存)

局部变量(包括函数内的变量)通常使用这种分配方式

  • 原理
    • C++ 中的自动内存管理主要依赖栈(Stack)。栈是一种数据结构,它按照后进先出(LIFO)的原则进行操作。当一个函数被调用时,编译器会在栈上为函数的局部变量和函数参数自动分配内存。这些变量的生命周期与函数的执行周期紧密相关。当函数开始执行时,栈指针向下移动(在内存地址上表现为减小),为变量开辟空间;当函数执行结束时,栈指针向上移动,这些局部变量所占用的空间会自动释放,无需程序员手动干预。
  • 示例
void function() {
    int a = 10;
    int b[5];
    // 在这里进行其他操作
}

在这个函数中,a是一个简单的int类型局部变量,b是一个包含 5 个int元素的局部数组。当function函数被调用时,编译器会在栈上为ab分配足够的空间。当函数执行结束后,这些空间会自动被释放。

  • 特点和限制
    • 优点是内存分配和释放速度非常快。因为栈的操作相对简单,只需要调整栈指针即可。
    • 然而,栈的大小是有限制的。如果在栈上分配过多的内存,例如定义一个极大的局部数组,可能会导致栈溢出(Stack Overflow)错误。这种内存管理方式适用于函数内部临时使用的变量,对于需要在函数调用结束后仍然保留的数据,栈内存就不适用了。

🍃2.静态内存管理(全局和静态变量)

全局变量、静态局部变量和静态数据成员使用这种分配方式。

静态内存管理在程序启动时就已经将内存确定好

  • 全局变量
    • 全局变量是在函数体外定义的变量。它们存储在全局 / 静态存储区。全局变量的作用域是从定义位置开始到整个文件结束(可以通过extern关键字扩展其作用域到其他文件)。其内存空间在程序启动时就已经分配好,并且在整个程序运行期间都存在。例如:
int global_variable = 10;

void function() {
    // 在这里可以访问和修改global_variable
    global_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变量。
#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 MyClassMyClass* 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;

    // 释放内存,这里使用delete
    delete 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在释放内存时会自动调用对象的析构函数。
      • newdelete是运算符,它们的操作是基于类型的,这使得代码更具类型安全性。
    • malloc/free
      • malloc 和 free 是 C 语言中的函数,用于在堆上分配和释放内存。malloc 只是简单地分配指定大小的内存块,例如void* p = malloc(sizeof(int));,它返回一个void*类型的指针,需要手动进行类型转换。free 函数只是释放由 malloc 分配的内存,不会调用对象的构造函数或析构函数。
  • 类型安全性
    • new/delete
      • 由于 new 和 delete 是基于类型的操作符,它们提供了更好的类型安全性。例如,new会根据要分配对象的类型来确定所需的内存大小,并且在编译时就可以检查类型相关的错误。如果试图用错误的类型指针来使用delete,编译器可能会发出警告。
    • malloc/free
      • malloc 和 free 的类型安全性相对较差。因为 malloc 返回的是一个void*指针,需要程序员手动进行类型转换。如果转换错误,可能会导致程序出现未定义行为,而且编译器很难在编译时发现这种错误。
  • 异常处理和错误返回
    • new/delete
      • 在 C++ 中,如果new分配内存失败(例如系统内存不足),会抛出一个bad_alloc类型的异常。这使得程序可以通过异常处理机制来应对内存分配失败的情况。
      • delete本身不会返回错误码,但是如果在错误的情况下使用(如释放未分配的内存或者多次释放同一块内存),会导致程序出现未定义行为。
    • malloc/free
      • malloc 在内存分配失败时返回nullptr,程序员需要检查这个返回值来确定是否分配成功。如果没有检查,使用nullptr指针可能会导致程序崩溃。free 在释放内存时不会返回错误码,同样,如果错误地使用会导致未定义行为。

五、内存管理注意事项

1.避免内存泄漏:

  • 确保在不再需要使用动态分配的内存时,及时使用deletedelete[]释放内存。
  • 例如,在使用完一个动态分配的对象后,一定要释放它的内存:
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 {
    // 使用 ptr
    delete ptr;
}

4.避免内存碎片:

  • 频繁的动态内存分配和释放可能会导致内存碎片,降低内存的可用性。可以考虑使用内存池等技术来减少内存碎片。

总之,C++ 的内存管理需要程序员谨慎处理,以确保程序的正确性和性能。

理解 C++ 的内存区域划分、管理方式以及注意事项,对于编写高质量的 C++ 程序至关重要。

本文完。

我的博客即将同步至腾讯云开发者社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=nhl9kkcrfoft

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

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

相关文章

聚类算法全面解析:理论与实践结合

聚类&#xff08;Clustering&#xff09;是数据挖掘和机器学习中一类重要的无监督学习方法&#xff0c;旨在将数据划分为多个类别&#xff0c;使得类别内部的数据相似度高&#xff0c;而类别之间的数据差异较大。聚类广泛应用于图像分割、市场分析、生物信息学、文本挖掘等领域…

Python 批量剪辑视频片头片尾工具

Python 批量剪辑视频片头片尾工具 1.简介&#xff1a; 批量剪辑片头片尾的软件&#xff0c;让你的视频创作事半功倍&#xff0c;视频剪辑处理完成后&#xff0c;用户可以在指定文件夹中查看已经剪切完片头片尾的视频‌。这些工具不仅适用于个人用户进行日常的视频编辑工作&am…

大模型分类1—按应用类型

版权声明 本文原创作者:谷哥的小弟作者博客地址:http://blog.csdn.net/lfdfhl根据应用领域,大模型可分为自然语言处理、计算机视觉和多模态大模型。 1. 自然语言处理大模型(NLP) 1.1 应用领域与技术架构 自然语言处理大模型(NLP)的应用领域广泛,包括但不限于文本分类、…

保姆级教程用vite创建vue3项目并初始化添加PrimeVue UI踩坑实录

文章目录 一、什么是PrimeVue二、详细教程1.添加PrimeVue2.配置main.js3.添加自动引入4.配置vite.config.js5.创建测试页面 一、什么是PrimeVue PrimeVue 是一个用于 Vue.js 3.x 开发的一款高质量、广受欢迎的 Web UI 组件库。 官网地址&#xff1a;https://primevue.org/ 二、…

Go的Gin比java的Springboot更加的开箱即用?

前言 隔壁组的云计算零零后女同事&#xff0c;后文简称 云女士 &#xff0c;非说 Go 的 Gin 框架比 Springboot 更加的开箱即用&#xff0c;我心想在 Java 里面 Springboot 已经打遍天下无敌手&#xff0c;这份底蕴岂是 Gin 能比。 但是云女士突出一个执拗&#xff0c;非我要…

php 系统函数 记录

PHP intval() 函数 PHP函数介绍—array_key_exists(): 检查数组中是否存在特定键名 如何使用PHP中的parse_url函数解析URL PHP is_array()函数详解&#xff0c;PHP判断是否为数组 PHP函数介绍&#xff1a;in_array()函数 strpos定义和用法 strpos() 函数查找字符串在另一字符串…

关于Chrome自动同步书签的解决办法

前言 并不一定适用所有用户&#xff0c; 目前我在网上搜集了一些资料&#xff0c;也做了一些尝试。 就我个人总结的经验来讲&#xff0c;分享大家以下几种办法&#xff1a; 1.书签同步插件 点击如下&#x1f517;&#xff1a; Chrome书签同步https://bm.famend.cn/ …

matrixzq:基于ℤq的纯python矩阵库

1. 引言 当希望使用纯 Python 代码对整数 q 模矩阵进行操作&#xff0c;以演示使用学习误差 (Learning-With-Errors&#xff0c;LWE) 的基于格的加密方案的一些原理时&#xff0c;找到了 Thom Ives 编写的优秀代码“纯 Python 中无需 Numpy 或 Scipy 的 BASIC 线性代数工具”&…

深度学习笔记——模型压缩和优化技术(蒸馏、剪枝、量化)

本文详细介绍模型训练完成后的压缩和优化技术&#xff1a;蒸馏、剪枝、量化。 文章目录 1. 知识蒸馏 (Knowledge Distillation)基本概念工作流程关键技术类型应用场景优势与挑战优势挑战 总结 2. 权重剪枝 (Model Pruning)基本原理二分类1. 非结构化剪枝&#xff08;Unstructur…

【单片机】ESP32-S3+多TMC2209控制步进电机系列1 UART通信及无传感回零 硬件部分

目录 1. 硬件选型1.1 esp32硬件型号1.2 TMC2209 硬件型号 2 原理接线图2.1 esp32接线2.2 TMC2209接线2.2.1 单向通讯 不配置地址2.2.2 单向通讯 配置地址2.2.3 双向通讯 单UART 【本文采用】2.2.4 双向通讯 多UART 3. 成品效果 1. 硬件选型 1.1 esp32硬件型号 采用的是微雪ES…

【论文复刻】雾霾污染及ZF治理与经济高质量发展(2004-2020年)

一、数据来源&#xff1a; PM2.5数据根据美国哥伦比亚大学社会经济数据与应用中心提供的全球PM2.5的年均浓度数据整理计算而得&#xff0c;人均实际GDP是以2000年为基期进行平减处理获得的实际GDP&#xff0c;控制变量来自《中国城市统计年鉴》、国家统计局&#xff0c;内含原…

行列式计算方法

行列式&#xff08;Determinant&#xff09;是线性代数中一个重要的概念&#xff0c;用来描述方阵的一些性质&#xff0c;尤其是与矩阵的可逆性、特征值等有关。下面是几种常见的计算行列式的方法&#xff1a; 1. 2x2矩阵的行列式 对于一个2x2矩阵&#xff1a; 行列式计算公式…

Elastic Cloud Serverless:深入探讨大规模自动扩展和性能压力测试

作者&#xff1a;来自 Elastic David Brimley, Jason Bryan, Gareth Ellis 及 Stewart Miles 深入了解 Elasticsearch Cloud Serverless 如何动态扩展以处理海量数据和复杂查询。我们探索其在实际条件下的性能&#xff0c;深入了解其可靠性、效率和可扩展性。 简介 Elastic Cl…

基于SpringBoot的旅游管理系统设计与实现

标题&#xff1a; 《基于SpringBoot的旅游管理系统设计与实现》 摘要&#xff1a; 本研究的主要目标是设计与实现基于Spring Boot的现代化旅游管理系统&#xff0c;旨在有效解决传统系统存在的多项问题&#xff0c;如用户体验不佳、功能不完善以及安全性方面的隐患。随着互联网…

LeetCode 热题100(十五)【动态规划】(3)

15.7最长递增子序列&#xff08;中等&#xff09; 题目描述&#xff1a;leetcode链接 300. 最长递增子序列 给你一个整数数组 nums &#xff0c;找到其中最长严格递增子序列的长度。 子序列 是由数组派生而来的序列&#xff0c;删除&#xff08;或不删除&#xff09;数组中的元…

精华帖分享|书中自有黄金屋系列2——格雷厄姆估值因子

本文来源于量化小论坛股票量化板块精华帖&#xff0c;作者为Benlyn&#xff0c;发布于2024年2月2日。 以下为精华帖正文&#xff1a; 01 前言 巴菲特一直强调“以合理的估值买入好公司”的投资理念&#xff0c;因此今天想给大家介绍一下与估值相关的内容。买股票买好公司固然…

干部谈话考察系统如何实现灵活定制和精准考评?

在当今社会&#xff0c;干部选拔与任用已成为各类组织内部管理的关键环节。为了确保选拔出的干部具备高素质和卓越能力&#xff0c;干部谈话考察系统应运而生。这一系统以其灵活定制和精准考评的特点&#xff0c;为组织提供了科学、高效的干部考察手段。 干部谈话考察系统通过集…

云渲染特效广告一秒费用预估是多少?

在计算云渲染特效广告每秒钟的费用时&#xff0c;我们需要综合考虑多个关键因素&#xff0c;包括特效的复杂性、所需的渲染计算能力以及对渲染质量的具体要求。通常情况下&#xff0c;影视特效级别的广告因其场景极其复杂&#xff0c;每帧渲染所需时间较长&#xff0c;从而导致…

利用docker-compose来搭建flink集群

1.前期准备 &#xff08;1&#xff09;把docker&#xff0c;docker-compose&#xff0c;kafka集群安装配置好 参考文章&#xff1a; 利用docker搭建kafka集群并且进行相应的实践-CSDN博客 这篇文章里面有另外两篇文章的链接&#xff0c;点进去就能够看到 &#xff08;2&…

2024年认证杯SPSSPRO杯数学建模C题(第一阶段)云中的海盐解题全过程文档及程序

2024年认证杯SPSSPRO杯数学建模 C题 云中的海盐 原题再现&#xff1a; 巴黎气候协定提出的目标是&#xff1a;在2100年前&#xff0c;把全球平均气温相对于工业革命以前的气温升幅控制在不超过2摄氏度的水平&#xff0c;并为1.5摄氏度而努力。但事实上&#xff0c;许多之前的…