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、基本…...
接口测试中缓存处理策略
在接口测试中,缓存处理策略是一个关键环节,直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性,避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明: 一、缓存处理的核…...
Spark 之 入门讲解详细版(1)
1、简介 1.1 Spark简介 Spark是加州大学伯克利分校AMP实验室(Algorithms, Machines, and People Lab)开发通用内存并行计算框架。Spark在2013年6月进入Apache成为孵化项目,8个月后成为Apache顶级项目,速度之快足见过人之处&…...
STM32标准库-DMA直接存储器存取
文章目录 一、DMA1.1简介1.2存储器映像1.3DMA框图1.4DMA基本结构1.5DMA请求1.6数据宽度与对齐1.7数据转运DMA1.8ADC扫描模式DMA 二、数据转运DMA2.1接线图2.2代码2.3相关API 一、DMA 1.1简介 DMA(Direct Memory Access)直接存储器存取 DMA可以提供外设…...
在四层代理中还原真实客户端ngx_stream_realip_module
一、模块原理与价值 PROXY Protocol 回溯 第三方负载均衡(如 HAProxy、AWS NLB、阿里 SLB)发起上游连接时,将真实客户端 IP/Port 写入 PROXY Protocol v1/v2 头。Stream 层接收到头部后,ngx_stream_realip_module 从中提取原始信息…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
(一)单例模式
一、前言 单例模式属于六大创建型模式,即在软件设计过程中,主要关注创建对象的结果,并不关心创建对象的过程及细节。创建型设计模式将类对象的实例化过程进行抽象化接口设计,从而隐藏了类对象的实例是如何被创建的,封装了软件系统使用的具体对象类型。 六大创建型模式包括…...
Rust 开发环境搭建
环境搭建 1、开发工具RustRover 或者vs code 2、Cygwin64 安装 https://cygwin.com/install.html 在工具终端执行: rustup toolchain install stable-x86_64-pc-windows-gnu rustup default stable-x86_64-pc-windows-gnu 2、Hello World fn main() { println…...
上位机开发过程中的设计模式体会(1):工厂方法模式、单例模式和生成器模式
简介 在我的 QT/C 开发工作中,合理运用设计模式极大地提高了代码的可维护性和可扩展性。本文将分享我在实际项目中应用的三种创造型模式:工厂方法模式、单例模式和生成器模式。 1. 工厂模式 (Factory Pattern) 应用场景 在我的 QT 项目中曾经有一个需…...
rknn toolkit2搭建和推理
安装Miniconda Miniconda - Anaconda Miniconda 选择一个 新的 版本 ,不用和RKNN的python版本保持一致 使用 ./xxx.sh进行安装 下面配置一下载源 # 清华大学源(最常用) conda config --add channels https://mirrors.tuna.tsinghua.edu.cn…...
python打卡第47天
昨天代码中注意力热图的部分顺移至今天 知识点回顾: 热力图 作业:对比不同卷积层热图可视化的结果 def visualize_attention_map(model, test_loader, device, class_names, num_samples3):"""可视化模型的注意力热力图,展示模…...
