C/C++内存详解

news2024/9/23 15:33:13

欢迎来到 破晓的历程的 博客

⛺️不负时光,不负己✈️

文章目录

    • C/C++内存模型
    • C语言动态内存管理
      • malloc
      • realloc
      • calloc
      • free
    • C++动态内存申请
      • new 操作符
      • delete 操作符
      • 注意事项
      • 用法示例
    • operator new和operator delete函数
    • 内存泄露

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);
}

你知道上面代码中定义的变量分别存储在内存中的哪些部分吗?
在这里插入图片描述
说明一下:

  1. 栈又叫做堆栈,用来存储非静态局部变量、函数参数和返回值等等,栈是向下增长的。
  2. 内存映射段是高效的IO映射的方式,用来装载一个共享的动态内存库,用户可调用接口创建共享内存,用于进程间通信。
  3. 堆用于存储运行时动态内存分配,堆是向上增长的。我们使用malloc动态内存申请的空间在堆上。包括我们一会儿讲到的new也是如此。
  4. 数据段又叫做静态区,用于存储全局变量静态数据
  5. 代码段又叫做常量区,用来存储可执行的代码只读常量

C语言动态内存管理

mallocrealloccallocfree 是C语言中用于动态内存管理的标准库函数,它们定义在<stdlib.h>头文件中。这些函数允许程序在运行时根据需要分配和释放内存,而不是在编译时静态地分配内存。这对于处理未知大小的数据或需要动态增长的数据结构(如链表、树等)特别有用。

malloc

malloc(Memory Allocation)函数用于动态分配一块指定大小的内存区域。其原型为:

void* malloc(size_t size);
  • size 参数指定了要分配的字节数。
  • 如果分配成功,返回指向分配的内存区域的指针;如果分配失败,则返回 NULL

使用 malloc 分配的内存区域是未初始化的,其内容是未定义的。

realloc

realloc(Re-Allocation)函数用于重新调整之前通过 malloccallocrealloc 分配的内存区域的大小。其原型为:

void* realloc(void* ptr, size_t size);
  • ptr 是指向要调整大小的内存区域的指针。如果 ptrNULL,则 realloc 的行为类似于 malloc,分配一块新的内存区域。
  • size 是新的大小。
  • 如果分配成功,返回指向新内存区域的指针(可能与原指针相同,也可能不同)。如果失败,则返回 NULL,但原内存区域不会被释放。

calloc

calloc(Contiguous Allocation)函数也用于动态分配内存,但它还会将分配的内存区域初始化为零。其原型为:

void* calloc(size_t num, size_t size);
  • num 指定了要分配的元素数量。
  • size 指定了每个元素的大小(以字节为单位)。
  • calloc 分配的内存总大小是 num * size
  • 分配的内存区域会被初始化为零。
  • 如果分配成功,返回指向分配的内存区域的指针;如果失败,则返回 NULL

free

free 函数用于释放之前通过 malloccallocrealloc 分配的内存区域。其原型为:

void free(void* ptr);
  • ptr 是指向要释放的内存区域的指针。
  • 一旦内存被释放,ptr 指针就成为悬垂指针(dangling pointer),不应再被使用。
  • 尝试访问已释放的内存区域是未定义行为,可能导致程序崩溃或数据损坏。

总的来说,mallocrealloccallocfree 提供了在C语言中进行动态内存管理的核心功能,允许程序在运行时灵活地管理内存资源。

面试题:malloc、realloc和calloc有什么区别?
malloc:动态申请空间,但不对空间进行初始化
realloc:对申请过的内存空间进行扩容处理。
calloc:申请空间的同时进行初始化处理,calloc=malloc+memset。

C++动态内存申请

C语言的动态内存申请函数对于C++依旧可以使用。但也引入了新的动态内存申请方式:new、delete。

注意:malloc、realloc和calloc属于函数,但是new和delete属于操作符

new 操作符

new 操作符用于在堆(heap)上动态分配内存,并调用对象的构造函数(如果有的话)。其基本语法有两种形式:

  1. 为单个对象分配内存

    TypeName* pointer = new TypeName(initializer);
    

    这里,TypeName 是要创建的对象类型,initializer 是传递给对象构造函数的参数(如果构造函数需要的话;如果构造函数没有参数或对象类型是基本数据类型,则可以省略)。pointer 是一个指向新创建对象的指针。

  2. 为对象数组分配内存

    TypeName* array = new TypeName[arraySize];
    

    这里,TypeName 是数组元素的类型,arraySize 是数组中元素的数量。array 是一个指向数组第一个元素的指针。注意,对于数组,不会调用构造函数来初始化每个元素(除非元素类型是类类型且该类提供了默认构造函数),而是进行默认初始化(对于类类型,调用默认构造函数;对于内置类型,不进行初始化)。

delete 操作符

delete 操作符用于释放之前通过 new 分配的内存,并调用对象的析构函数(如果有的话)。其语法也有两种形式,对应于 new 的两种用法:

  1. 释放单个对象

    delete pointer;
    

    这里,pointer 是指向之前通过 new 分配的内存的指针。使用 delete 后,pointer 变成了悬垂指针,不应再被使用。

  2. 释放对象数组

    delete[] array;
    

    这里,array 是指向之前通过 new[] 分配的内存的指针。注意,对于数组,必须使用 delete[] 而不是 delete 来释放内存,以确保为每个元素调用析构函数(如果元素类型是类类型的话)。

注意事项

  • 使用 new 分配的内存必须使用 delete(或 delete[])来释放,以避免内存泄漏。
  • 释放内存后,指针变成悬垂指针,不应再被解引用或用于其他内存操作。
  • 对于类类型的对象,new 会自动调用构造函数,delete 会自动调用析构函数。这是 new/deletemalloc/free 的一个重要区别。
  • 如果 new 表达式失败(例如,由于内存不足),它会抛出 std::bad_alloc 异常(在 <new> 头文件中定义)。因此,在使用 new 时,可能需要考虑异常处理。
  • 对于基本数据类型(如 intfloat 等),newdelete 主要用于分配和释放内存,不会调用任何特殊的构造函数或析构函数。然而,对于类类型,它们的行为更加复杂,因为它们涉及到对象的构造和析构。

用法示例

#include<iostream>
using namespace std;
int main()
{
	int* p1 = new int(10);//申请一个空间
	int* p2 = new int[10];//申请一个数值

	delete p1;//释放一个空间
	delete []p2;//释放一个数组
}

思考一下:既然已经有了malloc等函数,为什么还要设计出new这些操作符呢?new相对于malloc有哪些优势呢?

  • 当申请的空间类型为内置类型时,malloc和new的功能相同。
  • 如果内存申请失败,malloc会返回0,而new则会选择抛异常
  • 当申请的类型为自定义类型时,malloc和new的功能就有些差别了。接下来我们就介绍一下二者之间的差别。

让我们先来看看这段代码:

class A
{
public:
	A()
	{
		_a = 1;
		cout << "A()" << endl;
	}
public:
	int _a;
		
};
int main()
{
	A* a1 = (A*)malloc(sizeof(A));
	cout << "--------------------------------------" << endl;
	A* a2 = new A;

}

运行一下,我们会发现:
在这里插入图片描述
说明一下
对于自定义类型的对象,例如类对象,new对象的同时会调用构造函数对对象进行构造,delete对象的同时会调用析构函数对对象进行析构。

operator new和operator delete函数

new和delete是用户进行动态内存申请和释放的操作符,operator new 和operator
delete是系统提供的全局函数
,new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间。

operator new和operator delete在用法上和malloc和free完全一样,都会在堆上申请空间

用法如下:

int* p = (int*)operator new(sizeof(int) * 10);//申请10个int类型大小的空间
operator delete (p);//对申请的空间进行释放

其在用法上等价于:

int* q = (int*)malloc(sizeof(int) * 10);
free(p);

尽管operator new和malloc在用法和作用上非常相似。但是仍然有不同之处?

不同之处有如下:

处理错误的方式不同,让我们看看如下的代码:
在这里插入图片描述
在这里插入图片描述

总结一下:
在申请失败的情况下,malloc返回0,operator new抛异常。


malloc VS operator new VS new

  • operator new=malloc+抛异常
  • new=operator+初始化

内存泄露

什么是内存泄露?内存泄露有什么⚠️?

  • 什么是内存泄漏:内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。
  • 内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死。

内存泄露的分类

C/C++程序中一般我们关心两种方面的内存泄漏:

  • 堆内存泄漏(Heap leak)
    堆内存指的是程序执行中依据须要分配通过malloc / calloc / realloc / new等从堆中分配的一块内存,用完后必须通过调用相应的 free或者delete 删掉。假设程序的设计错误导致这部分内存没有被释放,那么以后这部分空间将无法再被使用,就会产生Heap Leak。
  • 系统资源泄漏
    指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。

如何避免内存泄露

  1. 工程前期良好的设计规范,养成良好的编码规范,申请的内存空间记着匹配的去释放。ps:
    这个理想状态。但是如果碰上异常时,就算注意释放了,还是可能会出问题。需要下一条智
    能指针来管理才有保证。
  2. 采用RAII思想或者智能指针来管理资源。
  3. 有些公司内部规范使用内部实现的私有内存管理库。这套库自带内存泄漏检测的功能选项。
  4. 出问题了使用内存泄漏工具检测。ps:不过很多工具都不够靠谱,或者收费昂贵。
    总结一下:

内存泄漏非常常见,解决方案分为两种:1、事前预防型。如智能指针等。2、事后查错型。如泄 漏检测工具。

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

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

相关文章

四、SPI——2、NOR FLASH

一、NOR FLASH介绍 FLASH是常用的用于储存数据的半导体器件&#xff0c;它具有容量大&#xff0c;可重复擦写、按“扇区/块”擦除、掉电后数据可继续保存的特性。 FLASH是有一个物理特性&#xff1a;只能写0&#xff0c;不能写1&#xff0c;写1靠擦除。 FLASH主要有NOR Flash和…

【JavaEE初阶】JVM内存划分和类加载过程以及垃圾回收

目录 &#x1f332;内存划分 &#x1f6a9;堆&#xff08;线程共享&#xff09; &#x1f6a9;栈 &#x1f6a9;元数据区 &#x1f343;类加载过程 &#x1f6a9;双亲委派模型 &#x1f384;垃圾回收机制&#xff08;GC&#xff09; &#x1f6a9;找到谁是垃圾(不被继续…

纷享销客CRM渠道分销之多维度数据分析介绍

预设渠道报表驾驶舱 基于渠道分销场景&#xff0c;系统预设了一个全面的渠道订货数据驾驶舱&#xff0c;旨在通过直观的数据分析&#xff0c;为企业提供深度的市场洞察和业务决策支持。该驾驶舱提供渠道订货的概览&#xff0c;快速把握整体订货动态。 渠道订货波动分析&#…

Scratch 角色绘制

引言 在Scratch这款强大的可视化编程环境中&#xff0c;不仅可以通过编程来实现各种有趣的互动项目&#xff0c;还能利用内置的绘图编辑器来创造独一无二的角色。本文将引导你如何使用Scratch中的绘图编辑器&#xff0c;绘制出属于你自己的简单图形角色。 准备工作 首先&#…

【数据分享】2000—2023年我国250米分辨率逐月植被覆盖度(FVC)栅格数据

植被覆盖度&#xff08;Fractional Vegetation Cover&#xff0c;简称FVC&#xff09;是指植被&#xff08;包括叶、枝、茎&#xff09;在水平地面的垂直投影面积占研究区总面积的百分比。植被覆盖度是生态学、地理学、气候学等多个学科研究的基础数据&#xff0c;对于理解生态…

多线程——创建

线程的创建与启动 Java中&#xff0c;所有的线程对象都必须是Thread类或其子类的实例。 三种创建方式&#xff1a; 集成Thread类创建线程类 继承Thread类&#xff0c;重写run方法&#xff0c;run方法的方法体代表线程需要完成的任务&#xff0c;称为线程执行体。 创建子类的实…

鸿蒙开发5.0【Code Linter实现代码检查】

Code Linter针对ArkTS/TS代码进行最佳实践/编程规范方面的检查。 检查方法&#xff1a; 编辑器自带Code Linter。 在已打开的代码编辑器窗口单击右键点击Code Linter&#xff0c;或在工程管理窗口中鼠标选中单个或多个工程文件/目录&#xff0c;右键选择Code Linter执行代码检…

2024年医疗器械企业5款CRM系统对比评测

医疗器械行业是一个多学科交叉、知识密集型、资金密集型的高新技术产业&#xff0c;进入门槛较高&#xff0c;产品种类繁多&#xff0c;技术含量较高。 随着医改的深入推进&#xff0c;医疗器械集采常态化成为行业新常态&#xff0c;中国的医疗器械行业不仅面临着巨大的市场潜…

zabbix6.4配置监控k8s 1.28集群

zabbix6.4配置监控rke2 rancher k8s集群 1. 说明1.1 为什么要使用zabbix6.x监控k8s1.2 部署环境1.3 部署前的一些问题 2. 使用helm3部署zabbix proxy和zabbix agent2.1 添加仓库2.2 修改配置2.3 部署2.4 确认部署情况 3. 在zabbix web页面配置连接zabbix proxy3.1 添加Proxy代理…

kali (linux) 配置windows远程桌面(mstsc.exe)连接

Kali 安装 tightvncserver 一、软件说明 1) tightvncserver是一个轻量级&#xff0c;只能建立桌面&#xff0c;不能查看TTY7/TTY1正在显示的桌面,但x11vnc可以&#xff0c;相比x11vnc安全传输差一些。反之&#xff0c;x11 vnc:安全传输较好&#xff0c;但占用资源比 tightvncs…

PB级内存计算项目实战-富华保险

一、保险项目的基本介绍 项目名称:富华阳光人寿保险 1. 行业背景介绍 在保险行业中,最为核心技术就是精算,精算简单来说就是根据人的年龄来计算应交保费问题,通过精算,让整个保险行业更加专业化,精细化 从而取代之间依靠经验判断的方式 精算到目前为止,并不仅仅计算保费,主要包…

Leetcode 1143. 最长公共子序列 记忆化搜索 优化 C++实现

Leetcode 1143. 最长公共子序列 问题&#xff1a;给定两个字符串 text1 和 text2&#xff0c;返回这两个字符串的最长公共子序列的长度。如果不存在公共子序列&#xff0c;返回 0 。 一个字符串的 子序列 是指这样一个新的字符串&#xff1a;它是由原字符串在不改变字符的相对…

一体化智能电动窗帘:开启智能生活新时尚

史新华 在科技不断进步的今天&#xff0c;人们对生活品质的追求也越来越高。电动窗帘作为智能家居的重要组成部分&#xff0c;以其便捷、智能、时尚的特点&#xff0c;正逐渐走进千家万户。 添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09; 一、电动窗帘…

KEYSIGHT U2020 X系列 USB峰值和均值功率传感器

​ _是德(KEYSIGHT) _ U2020 X系列 USB峰值和均值功率传感器 苏州新利通仪器仪表 U2020 X 系列功率传感器得到 Keysight BenchVue 软件的支持。使用 BenchVue 软件&#xff0c;您无需编程便可轻松控制功率计记录数据&#xff0c;并以各种形式显示测量结果。 只需将传感器…

AI大模型与量子纠缠理论的结合,以及相关应用思考

大家好&#xff0c;我是微学AI&#xff0c;今天给大家介绍一下AI大模型与量子纠缠理论的结合&#xff0c;以及相关应用思考。将大模型&#xff08;LLM&#xff09;的基本原理与量子纠缠理论相结合是一个高度抽象的概念。我们首先需要理解这两个领域的基本原理&#xff0c;然后探…

#ARM开发 笔记

课程介绍 ARM开发 --> Linux移植 --> 驱动开发 前后联系&#xff1a;ARM和系统移植为驱动开发学习做准备工作 所需知识&#xff1a;C语言基础及STM32需要的硬件知识 学习方法 学习流程、思想和解决问题的方法即可 知道驱动的基本框架以及基本开发要求 底层课程导学 接口技…

NTFS安全权限和文件共享

一.常见文件系统 NTFS 描述&#xff1a; Windows最常使用的文件系统&#xff08;New Technology File System&#xff09;微软公司开发的一种专用于 Windows 操作系统的文件系统。 特点&#xff1a; 效率性 可以提高磁盘的读写性能&#xff1b; 可靠性 加密文件系统访问控制列…

Vue组件:使用Prop实现父组件向子组件传递数据

1、Prop 基本用法 由于组件实例的作用域是孤立的&#xff0c;因此子组件的模板无法直接应用父组件的数据。如果想要通过父组件向子组件传递数据&#xff0c;就需要定义 Prop。Prop 是父组件用来传递数据的一个自定义属性&#xff0c;这样的属性需要定义在组件选项对象的 props…

并发集合(二):CopyOnWriteArrayList

1、CopyOnWriteArrayList介绍 CopyOnWriteArrayList 是一个线程安全的ArrayList。 CopyOnWriteArrayList 是基于Lock锁和线程副本的形式来保证线程安全的&#xff0c; 在写数据时&#xff0c;先获取Lock锁&#xff0c;然后复制一个副本&#xff0c;添加数据时&…

Delphi7实现Json对象的序列化与反序列化

在高版本的 Delphi 中&#xff0c;实现序列化和反序列化非常简单。然而&#xff0c;在 Delphi 7 中&#xff0c;这个过程仍然需要一些额外的努力。为了简化这个问题&#xff0c;我花了一些时间封装了一个支持序列化和反序列化的 JSON 解析库。 type{$M}TStartupParameters cla…