CSDN 博客:CC++ 内存管理详解
CSDN 博客:C/C++ 内存管理详解
在软件开发过程中,内存管理是一个非常重要的环节。对于 C 和 C++ 这两种编程语言,它们都拥有独特的内存管理机制,理解这些机制对于编写高效、健壮的程序至关重要。本文将详细讲解 C/C++ 内存管理相关的内容,并重点分析不同内存分配方式的区别和使用场景。
1. C/C++ 内存分布
在 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);
}
以下是对应变量在内存中的分布情况:
变量名 | 存储位置 | 存储段 |
---|---|---|
globalVar | 全局变量 | 数据段(静态区) |
staticGlobalVar | 静态全局变量 | 数据段(静态区) |
staticVar | 静态局部变量 | 数据段(静态区) |
localVar | 局部变量 | 栈 |
num1 | 局部数组 | 栈 |
char2 | 字符数组 | 栈 |
*char2 | 数组元素存储位置 | 栈 |
pChar3 | 指针变量 | 栈 |
*pChar3 | 常量字符串 “abcd” | 代码段(常量区) |
ptr1 | 指针变量 | 栈 |
*ptr1 | 动态分配内存 | 堆 |
ptr2 | 指针变量 | 栈 |
*ptr2 | 动态分配内存 | 堆 |
ptr3 | 指针变量 | 栈 |
*ptr3 | 动态分配内存 | 堆 |
内存区域分类:
- 栈(Stack):存储局部变量(如
localVar
),以及函数调用时的参数和返回值。 - 堆(Heap):存储动态分配的内存(如通过
malloc
、calloc
、realloc
分配的内存)。 - 数据段(Data Segment):存储全局变量和静态变量(如
globalVar
和staticGlobalVar
)。 - 代码段(Code Segment):存储程序的可执行代码以及只读常量(如
pChar3
所指向的字符串)。
2. C语言中的动态内存管理
C 语言提供了几种用于动态分配内存的函数:malloc
、calloc
、realloc
和 free
。这些函数用于在程序运行时动态地分配和释放内存。
2.1 malloc、calloc 和 realloc 的区别
- malloc:用于分配指定大小的内存块,内存中的内容未初始化。
- calloc:类似于
malloc
,但会将内存初始化为零。它的参数为元素的数量和每个元素的大小。 - realloc:用于调整之前分配的内存块的大小,如果新大小大于原大小,可能会移动内存块的位置。
示例代码:
int* ptr1 = (int*)malloc(sizeof(int) * 4); // 分配4个int类型大小的内存块
int* ptr2 = (int*)calloc(4, sizeof(int)); // 分配并初始化4个int类型大小的内存块
int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4); // 重新分配内存
free(ptr1);
free(ptr3);
2.2 malloc 实现原理
malloc
底层通常通过操作系统的 brk
或 mmap
系统调用分配内存。具体实现可能因平台和 C 标准库的不同而有所区别。在 GNU C 库(glibc)中,malloc
通过维护一个自由链表来跟踪已分配和未分配的内存块,并根据请求的大小寻找合适的内存块进行分配。
3. C++ 内存管理
C++ 继承了 C 语言的内存管理方式,并在此基础上引入了 new
和 delete
操作符,提供更方便的动态内存管理机制。与 malloc
和 free
不同,new
和 delete
适用于对象的动态内存分配,并且会自动调用构造函数和析构函数。
3.1 new 和 delete 操作符
- new:用于动态分配内存并调用对象的构造函数。
- delete:用于释放动态分配的内存并调用对象的析构函数。
示例代码:
int* ptr = new int; // 动态分配一个int类型的空间
delete ptr; // 释放内存int* arr = new int[10]; // 动态分配10个int类型的空间
delete[] arr; // 释放连续内存
3.2 new 和 delete 的实现原理
在 C++ 中,new
操作符会首先调用 operator new
函数来分配内存,然后在该内存上调用构造函数。而 delete
操作符则会先调用析构函数清理对象资源,再调用 operator delete
函数释放内存。
void* operator new(size_t size) {void* p = malloc(size);if (!p) throw std::bad_alloc(); // 如果分配失败,抛出异常return p;
}void operator delete(void* p) {free(p); // 释放内存
}
4. new 和 malloc 的区别
虽然 new
和 malloc
都可以用于动态分配内存,但它们有以下几点不同:
new
是操作符,malloc
是函数:new
是 C++ 内置的操作符,而malloc
是 C 语言中的标准库函数。- 初始化:
malloc
只分配内存,不会对其进行初始化;而new
不仅分配内存,还会调用构造函数初始化对象。 - 异常处理:
malloc
分配失败时返回NULL
,而new
分配失败时会抛出std::bad_alloc
异常。 - 自定义类型:
malloc
不能调用构造函数,因此不适合分配自定义类型的对象,而new
则可以调用构造函数来初始化对象。
4.1 示例代码:
class A {
public:A(int a = 0) : _a(a) {std::cout << "A() called" << std::endl;}~A() {std::cout << "~A() called" << std::endl;}
private:int _a;
};A* obj1 = (A*)malloc(sizeof(A)); // 使用 malloc 分配内存,但不会调用构造函数
free(obj1); // 释放内存A* obj2 = new A(10); // 使用 new 分配内存并调用构造函数
delete obj2; // 释放内存并调用析构函数
5. operator new 与 operator delete
operator new
和 operator delete
是全局函数,用于实现 new
和 delete
的底层操作。它们通常会调用 malloc
和 free
来完成内存的分配与释放。
**### CSDN 博客:C/C++ 内存管理详解(续)
接着上篇,我们已经介绍了 C/C++ 的内存分布和基本的动态内存管理方式。本篇将继续详细讲解内存管理中的一些高级内容,包括 operator new
和 operator delete
的实现原理、内置类型和自定义类型的内存管理、placement new
、以及 malloc/free
与 new/delete
的更深入对比。
5. operator new 与 operator delete
operator new
和 operator delete
是系统提供的全局函数,分别用于动态分配和释放内存。它们实际上是 new
和 delete
操作符的底层实现。在 C++ 中,new
操作符首先调用 operator new
分配内存,然后调用构造函数初始化对象;而 delete
操作符首先调用析构函数清理对象,然后调用 operator delete
释放内存。
5.1 operator new 的实现原理
operator new
的实现原理可以用如下代码描述:
void* operator new(size_t size) {void* p;// 尝试分配 size 字节的内存while ((p = malloc(size)) == nullptr) {// 如果 malloc 分配失败,尝试执行内存不足的应对措施if (_callnewh(size) == 0) {// 如果没有用户设置的处理措施,抛出 std::bad_alloc 异常throw std::bad_alloc();}}return p;
}
可以看到,operator new
本质上是通过 malloc
来分配内存的。不同的是,如果内存分配失败,operator new
会尝试调用用户设置的内存不足处理程序(_callnewh()
),而 malloc
只是简单返回 NULL
。
5.2 operator delete 的实现原理
operator delete
的实现则相对简单,它直接调用 free
来释放内存:
void operator delete(void* p) {free(p);
}
6. new 和 delete 的实现原理
6.1 内置类型的内存管理
对于内置类型(如 int
、float
等),new
和 malloc
在内存分配上是类似的。它们都分配指定大小的内存并返回指向该内存的指针。然而,new
与 malloc
的不同之处在于:
- 单个元素的分配:
new
可以分配单个内置类型的内存,而malloc
只能分配一块指定大小的内存。 - 异常处理:当内存分配失败时,
new
会抛出异常,而malloc
则返回NULL
。
示例代码:
int* p1 = new int; // 分配单个int类型空间
delete p1; // 释放内存int* p2 = (int*)malloc(sizeof(int)); // 使用malloc分配内存
free(p2); // 释放内存
6.2 自定义类型的内存管理
对于自定义类型,new
和 delete
的作用更加明显,因为它们除了分配和释放内存之外,还会自动调用构造函数和析构函数。这一特性使得 new
和 delete
成为管理复杂对象的首选。
6.2.1 new 的工作过程:
- 调用
operator new
分配内存:为对象分配所需的内存。 - 在已分配的内存上调用构造函数:通过构造函数来初始化对象。
6.2.2 delete 的工作过程:
- 调用析构函数:析构函数会清理对象占用的资源(如释放动态分配的内存等)。
- 调用
operator delete
释放内存:通过free
或类似的机制将内存归还给操作系统。
示例代码:
class A {
public:A(int a) : _a(a) {std::cout << "Constructor called" << std::endl;}~A() {std::cout << "Destructor called" << std::endl;}
private:int _a;
};int main() {A* obj = new A(10); // 动态分配并调用构造函数delete obj; // 调用析构函数并释放内存
}
7. malloc/free 和 new/delete 的区别
malloc/free
和 new/delete
都是从堆上分配内存,并且都需要用户手动释放,但它们之间存在一些关键区别:
7.1 语法上的区别
malloc/free
是函数:malloc
和free
是 C 标准库中的函数,用于动态内存管理。new/delete
是操作符:new
和delete
是 C++ 的内置操作符,主要用于对象的动态内存管理。
7.2 初始化的区别
malloc
不会初始化内存:malloc
只是分配一块内存,而不负责初始化内容。new
可以调用构造函数初始化对象:new
不仅分配内存,还会调用构造函数来初始化对象。
7.3 内存分配失败的处理方式
malloc
分配失败返回NULL
:如果malloc
无法分配内存,它会返回NULL
,程序员需要手动检查返回值。new
分配失败抛出std::bad_alloc
异常:当new
失败时,它会抛出异常,而不是返回NULL
。
7.4 自定义类型的对象分配
malloc/free
不会调用构造函数和析构函数:malloc
仅仅分配内存,无法初始化对象,也不会调用析构函数来清理对象的资源。new/delete
会调用构造函数和析构函数:new
在分配内存后会调用构造函数,delete
在释放内存前会调用析构函数。
示例代码对比:
// 使用 malloc/free
A* obj1 = (A*)malloc(sizeof(A)); // 仅仅分配内存,不调用构造函数
free(obj1); // 仅仅释放内存,不调用析构函数// 使用 new/delete
A* obj2 = new A(10); // 分配内存并调用构造函数
delete obj2; // 调用析构函数并释放内存
8. 定位 new 表达式 (Placement-new)
定位 new
表达式是一种高级用法,它允许在已分配的内存上构造对象,而不需要重新分配内存。典型的使用场景是内存池或者需要精细控制内存分配的地方。
8.1 定位 new 的使用方式
定位 new
表达式的语法如下:
new (place_address) type;
其中 place_address
是内存的地址,而 type
是需要构造的对象类型。
示例代码:
class A {
public:A(int a = 0) : _a(a) {std::cout << "A() called" << std::endl;}~A() {std::cout << "~A() called" << std::endl;}
private:int _a;
};int main() {void* buffer = malloc(sizeof(A)); // 分配一块内存A* obj = new(buffer) A(10); // 在指定的内存上构造对象obj->~A(); // 手动调用析构函数free(buffer); // 释放内存
}
8.2 定位 new 的应用场景
定位 new
常用于内存池管理。内存池是一种预先分配大量内存的技术,通过在这块内存上手动管理对象的分配和释放,能够极大提高程序的性能。特别是对于实时系统或嵌入式系统,使用内存池可以避免频繁调用操作系统的内存管理函数,减少系统开销。
结语
通过以上两部分的详细讲解,我们全面介绍了 C/C++ 的内存管理机制。从基本的 malloc/free
到 new/delete
,再到定位 new
表达式,内存管理的不同方式各有适用场景。理解并合理使用这些机制,能够帮助开发者编写高效且健壮的代码,特别是在需要精确控制内存分配的场景中,正确的内存管理能够极大提升程序的性能。
在实践中,建议程序员根据具体需求选择适当的内存管理方式,避免内存泄漏和资源浪费,确保程序的健壮性和可维护性。
相关文章:
CSDN 博客:CC++ 内存管理详解
CSDN 博客:C/C 内存管理详解 在软件开发过程中,内存管理是一个非常重要的环节。对于 C 和 C 这两种编程语言,它们都拥有独特的内存管理机制,理解这些机制对于编写高效、健壮的程序至关重要。本文将详细讲解 C/C 内存管理相关的内…...

表单制作代码,登录动画背景前端模板
炫酷动效登录页 引言 在网页设计中,按钮是用户交互的重要元素之一。一个炫酷的按钮特效不仅能提升用户体验,还能为网页增添独特的视觉吸引力。今天,我们将通过CSS来实现一个“表单制作代码,登录动画背景前端模板”。该素材呈现了数据符号排版显示出人形的动画效果,新颖有…...

嵌入式项目:STM32刷卡指纹智能门禁系统
本文详细介绍基于STM32的刷卡指纹智能门禁系统。 获取资料/指导答疑/技术交流/选题/帮助,请点链接: https://gitee.com/zengzhaorong/share_contact/blob/master/stm32.txt 1 系统功能 1.1 功能概述 本系统由STM32硬件端(下位机)…...
LeetCode 热题100 141. 环形链表
LeetCode 热题100 | 141. 环形链表 大家好,今天我们来解决一道经典的算法题——环形链表。这道题在 LeetCode 上被标记为简单难度,要求我们判断一个链表中是否存在环。下面我将详细讲解解题思路,并附上 Python 代码实现。 题目描述 给定一个…...
以绘图(绘制点、直线、圆、椭圆、多段线)为例子 通过设计模式中的命令模式实现
为了在命令模式的基础上实现撤销(Undo)和回退(Redo)功能,我们可以在每个命令类中记录一些必要的状态,允许我们撤销之前的操作,并在需要时回退操作。常见的做法是使用一个命令堆栈来存储历史命令…...

鹏哥c语言数组(初阶数组)
前言: 对应c语言视频54集 内容: 一维数组的创建 数组是一组相同元素的集合, 数组的创建方式 type_t就是数组的元素类型,const_n是一个常量表达式,用来指定数组的大小 c99标准之前的,数组的大小必须是…...

利用go-migrate实现MySQL和ClickHouse的数据库迁移
1. 背景 在使用gorm时 , 尽管已经有了自动建表和钩子函数 . 但是在面临希望了解到数据库的变更 , 和插入一些系统字段时 , 以及最关键的数据库迁移的工作 . gorm显得稍微有点不便 . 在了解到migrate这项技术后 , 就使用go-migrate开发了一个可以迁移MySQL和ClickHouse数据库的…...

计算机毕业设计SpringBoot+Vue.js企业客户管理系统(源码+LW文档+PPT+讲解+开题报告)
温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 作者简介:Java领…...
jmeter 如何做移动端的测试 特别是兼容性测试
JMeter本身主要是一款用于性能测试和功能测试的工具,虽然它并非专门为移动端测试设计,但可以通过一些方式来对移动端应用进行测试,以下从测试准备、测试过程及注意事项等方面为你详细介绍: 一、测试准备 (一)环境搭建 JMeter安装与配置:确保JMeter已经正确安装在测试机…...

深度学习技术全景图:从基础架构到工业落地的超级进化指南
🔍 目录导航 基础架构革命训练优化秘技未来战场前瞻 🧩 一、基础架构革命 1.1 前馈神经网络(FNN) ▍核心结构 import torch.nn as nnclass FNN(nn.Module):def __init__(self):super().__init__()self.fc1 nn.Linear(784, 25…...

vllm部署LLM(qwen2.5,llama,deepseek)
目录 环境 qwen2.5-1.5b-instruct 模型下载 vllm 安装 验证安装 vllm 启动 查看当前模型列表 OpenAI Completions API(文本生成) OpenAI Chat Completions API(chat 对话) vllm 进程查看,kill llama3 deep…...

基于SpringBoot的“古城景区管理系统”的设计与实现(源码+数据库+文档+PPT)
基于SpringBoot的“古城景区管理系统”的设计与实现(源码数据库文档PPT) 开发语言:Java 数据库:MySQL 技术:SpringBoot 工具:IDEA/Ecilpse、Navicat、Maven 系统展示 系统整体功能图 系统首页界面 系统注册界面 景…...
如何防止 Docker 注入了恶意脚本
根据您的描述,攻击者通过 CentOS 7 系统中的 Docker 注入了恶意脚本,导致自动启动名为 “masscan” 和 “x86botnigletjsw” 的进程。这些进程可能用于网络扫描或其他恶意活动。为了解决这一问题,建议您采取以下步骤: 1. 停止并删…...

使用python接入腾讯云DeepSeek
本文主要从提供SSE方式接入DeepSeek,并通过fastapi websocket对外提供接入方法。 参考文档: 腾讯云大模型:https://cloud.tencent.com/document/product/1759/109380 fastAPI官网:https://fastapi.tiangolo.com/ WebSocketManager…...

【MySQL】服务正在启动或停止中,请稍候片刻后再试一次【解决方案】
问题呈现 在使用MySQL的过程中我们可能会遇到以上的情况 解决方法 首先以管理员身份打开命令行窗口,注意是管理员身份,不然无权限访问。输入命令tasklist| findstr "mysql",用于查找mysql的残留进程。这个时候我们就会看到一个…...
测试工程师玩转DeepSeek之Prompt
以下是测试工程师使用DeepSeek的必知必会提示词指南,分为核心场景和高效技巧两大维度: 一、基础操作提示模板 1. 测试用例生成 "作为[金融系统/物联网设备/云服务]测试专家,请为[具体功能模块]设计测试用例,要求࿱…...

【PyTorch】2024保姆级安装教程-Python-(CPU+GPU详细完整版)-
一、准备工作 pytorch需要python3.6及以上的python版本 我是利用Anaconda来管理我的python。可自行安装Anaconda。 Anaconda官网 Free Download | Anaconda 具体Anaconda安装教程可参考 https://blog.csdn.net/weixin_43412762/article/details/129599741?fromshareblogdet…...

精选案例展 | 智己汽车—全栈可观测驱动智能化运营与成本优化
本案例为“观测先锋 2024 可观测平台创新应用案例大赛”精选案例,同时荣获IT168“2024技术卓越奖评选-年度创新解决方案”奖。 项目背景 近年来,中国汽车行业进入转型升级阶段,智能网联技术成为行业发展的核心。车联网、自动驾驶等技术的加速…...
MySQL 使用 `WHERE` 子句时 `COUNT(*)`、`COUNT(1)` 和 `COUNT(column)` 的区别解析
文章目录 1. COUNT() 函数的基本作用2. COUNT(*)、COUNT(1) 和 COUNT(column) 的详细对比2.1 COUNT(*) —— 统计所有符合条件的行2.2 COUNT(1) —— 统计所有符合条件的行2.3 COUNT(column) —— 统计某一列非 NULL 的记录数 3. 性能对比3.1 EXPLAIN 分析 4. 哪种方式更好&…...
Linux运维——网络管理
Linux网络管理 一、Linux网络应用要点二、命令常见用法2.1、curl2.1.1、发送GET请求2.1.2、发送POST请求2.1.3、设置请求头2.1.4、处理cookies2.1.5、处理重定向2.1.6、调试和详细信息2.1.7、使用代理2.1.8、文件上传2.1.9、其它常用选项2.1.10、综合示例 2.2、wget2.2.1、基本…...

【项目实战】通过多模态+LangGraph实现PPT生成助手
PPT自动生成系统 基于LangGraph的PPT自动生成系统,可以将Markdown文档自动转换为PPT演示文稿。 功能特点 Markdown解析:自动解析Markdown文档结构PPT模板分析:分析PPT模板的布局和风格智能布局决策:匹配内容与合适的PPT布局自动…...

相机从app启动流程
一、流程框架图 二、具体流程分析 1、得到cameralist和对应的静态信息 目录如下: 重点代码分析: 启动相机前,先要通过getCameraIdList获取camera的个数以及id,然后可以通过getCameraCharacteristics获取对应id camera的capabilities(静态信息)进行一些openCamera前的…...

10-Oracle 23 ai Vector Search 概述和参数
一、Oracle AI Vector Search 概述 企业和个人都在尝试各种AI,使用客户端或是内部自己搭建集成大模型的终端,加速与大型语言模型(LLM)的结合,同时使用检索增强生成(Retrieval Augmented Generation &#…...
【生成模型】视频生成论文调研
工作清单 上游应用方向:控制、速度、时长、高动态、多主体驱动 类型工作基础模型WAN / WAN-VACE / HunyuanVideo控制条件轨迹控制ATI~镜头控制ReCamMaster~多主体驱动Phantom~音频驱动Let Them Talk: Audio-Driven Multi-Person Conversational Video Generation速…...

网站指纹识别
网站指纹识别 网站的最基本组成:服务器(操作系统)、中间件(web容器)、脚本语言、数据厍 为什么要了解这些?举个例子:发现了一个文件读取漏洞,我们需要读/etc/passwd,如…...
JS设计模式(4):观察者模式
JS设计模式(4):观察者模式 一、引入 在开发中,我们经常会遇到这样的场景:一个对象的状态变化需要自动通知其他对象,比如: 电商平台中,商品库存变化时需要通知所有订阅该商品的用户;新闻网站中࿰…...

排序算法总结(C++)
目录 一、稳定性二、排序算法选择、冒泡、插入排序归并排序随机快速排序堆排序基数排序计数排序 三、总结 一、稳定性 排序算法的稳定性是指:同样大小的样本 **(同样大小的数据)**在排序之后不会改变原始的相对次序。 稳定性对基础类型对象…...

【Linux】Linux 系统默认的目录及作用说明
博主介绍:✌全网粉丝23W,CSDN博客专家、Java领域优质创作者,掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域✌ 技术范围:SpringBoot、SpringCloud、Vue、SSM、HTML、Nodejs、Python、MySQL、PostgreSQL、大数据、物…...
Python Einops库:深度学习中的张量操作革命
Einops(爱因斯坦操作库)就像给张量操作戴上了一副"语义眼镜"——让你用人类能理解的方式告诉计算机如何操作多维数组。这个基于爱因斯坦求和约定的库,用类似自然语言的表达式替代了晦涩的API调用,彻底改变了深度学习工程…...
深入浅出Diffusion模型:从原理到实践的全方位教程
I. 引言:生成式AI的黎明 – Diffusion模型是什么? 近年来,生成式人工智能(Generative AI)领域取得了爆炸性的进展,模型能够根据简单的文本提示创作出逼真的图像、连贯的文本,乃至更多令人惊叹的…...