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

c++动态内存分布以及和C语言的比较

 

 

文章目录

  • 前言
  • 一.c/c++内存分布
  •      C语言的动态内存管理方式
  •      C++内存管理方式
  •      operator new和operator delete函数
  •      malloc/free和new/delete的区别
  •      定位new
  •      内存泄漏的危害
  • 总结

 


前言

c++是在c的基础上开发出来的,所以关于内存管理这一方面是兼容c的,比如以前C语言的malloc等等都是可以继续使用的,但是靠C语言以前的东西是解决不了c++中的问题的,比如自定义类型空间的开辟以及自定义类型如何释放空间等等,所以在内存管理这方面c++又与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);
}

575a52528f1247e99dcc182010a132be.png 首先globalvar是全局变量,全局变量的static修饰的变量都放在静态区,staticGlobalVar既是全局变量又是静态变量所以还是放在静态区,staticVar虽然在局部作用域中但是静态变量都是放在静态区的,localVar是局部变量,局部变量放在栈中,num1是存放10个整形的数组也放在栈中。所以前5个的答案是C C C A A 

char 2[]是一个数组,同理是放在栈中的,*char2对数组名进行解引用,数组名代表首元素地址,也就是a存放的地方,数组都在栈中那么a肯定也是存放在栈中了。pchar3是一个指针变量,局部变量都是存放在栈中的,*pchar3是对这个指针变量进行解引用,解引用后找到其实际内容,虽然变量是在栈中开辟的,但是由于保存的是常量所以常量是放在常量区,所以*ptr3在代码段中。ptr1同样是指针变量,指针变量在栈中开辟,*ptr1是ptr1所指向的空间,使用malloc开辟的空间是在堆中,所以*ptr1是在堆中。这6道题的答案为:A A A D A B

187b629a4e7a44b296c22493d48d0d7e.png

 num1是个有10整形的数组,所以大小为40.char2是个字符数组,存放abcd\0共占用5个字节。strlen(char2)为4因为strlen会算到\0之前不包含\0.pchar3是个指针,指针是4/8字节。pchar3的长度也为4,ptr1是个指针大小也为4/8。所以答案为:40 5 4 4/8 4 4/8

栈又叫堆栈,非静态局部变量/函数参数/返回值等等,栈是向下增长的。

内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库,用户可使用系统接口创建共享共享内存,做进程间通信。

堆用于程序运行时动态内存分配,堆是可以上增长的。

数据段:存储全局数据和静态数据

代码段:可执行的代码/只读常量

C语言的动态内存管理方式

C语言使用malloc/calloc/realloc/free管理内存,那么malloc/calloc/realloc有什么区别呢?

malloc开辟空间不会进行初始化,calloc会初始化为0。realloc分为原地扩容和异地扩容,当原来扩容的空间后面有足够的空间的时候就在原来空间的后面直接扩容,如果空间不够则先找到一块足够大的空间然后将原来空间的数据拷贝到新空间再将旧空间释放掉。

c++内存管理方式

C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力,而且使用起来比较麻烦,因
此C++又提出了自己的内存管理方式:通过newdelete操作符进行动态内存管理
注意:申请和释放单个元素的空间,使用newdelete操作符,申请和释放连续的空间,使用
new[]delete[],注意:匹配起来使用。
下面讲解new和delete如何使用:
3a2df7fea1134949997e672717f67fed.png

2c110e2deff64c22afc2c15504e11817.png 当然new是可以手动初始化的,方式如下:

3a3d557dd85f4a2180559e9536228979.png

那么开连续的空间是什么样的呢?

8f95a0a3bf2943db9eb789e7291f857c.png 当然连续的也可以初始化,方式如下:

7a8d858d9de54e1a9788bdabc00c7280.png

我们使用new开辟空间一定要用delete去释放,尤其是自定义类型的时候因为delete会调用析构函数如果你在这里使用free释放空间,那么析构函数中没有释放的空间就会造成内存泄漏。

class A
{
public:A(int a = 0): _a(a){cout << "A():" << this << endl;}~A(){cout << "~A():" << this << endl;}
private:int _a;
};
int main()
{A* pp = new A(1);delete pp;return 0;
}

8b9f0721007e49048a5b51620a7516d1.png

 operator new与operator delete 函数

我们听名字以为这是运算符重载,其实不是这里只是用了一样的词罢了,这里与运算符重载没有任何关联。new和delete是用户进行动态内存申请和释放的操作符,operator new和operator delete是系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete释放空间。当然还有operator new[]和operator delete[]是为了匹配new【】和delete【】。

e92057412fc64d158e4ceef0db63cf26.png

7d77ec766fc44c59ac24bae16ab3b964.png 那么operator new和operator delete的底层是怎么实现的呢?

operator new:该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回,申请空间失败,尝试执行空间不足应对措施,如果该应对措施用户设置了,则继续申请,否则抛异常。

通过上述两个全局函数的实现知道,operator new 实际也是通过malloc来申请空间,如果
malloc申请空间成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施
就继续申请,否则就抛异常。operator delete 最终是通过free来释放空间的。
那么new和delete的底层既然是通过malloc和free实现的为什么不建议混用呢?这里拿自定义类型举个例子:
class Stack
{
public:Stack(){cout << "stack()" << endl;_a = new int[4];top = 0;capacity = 4;}~Stack(){cout << "~Stack" << endl;delete[] _a;_a = nullptr;top = capacity = 0;}private:int* _a;int top;int capacity;
};
int main()
{Stack sl;  //自定义类型会去调用构造和析构//这里的指针类型为内置类型,所以需要手动释放空间Stack* pst = new Stack;delete pst;return 0;
}

如上图所示,指针类型为内置类型不管是什么类型的指针都是内置类型所以不会像自定义类型一样调用构造和析构,当我们用pst开辟空间的时候和sl有什么不一样呢?看下图:

4e74b8c02a9547e3bb7686890160ee84.png

如果我们混着用比如释放的时候用free,free是不会调用析构函数的所以堆中绿色_a的空间没办法释放直接将pst的空间释放后再也找不到_a的空间了,这就造成了内存泄漏。

malloc/free和new/delete的区别 

1.malloc/free对于自定义类型不会调用其构造和析构函数,new/delete对于自定义类型会调用其构造函数和析构函数。

2.malloc开辟空间的时候不能初始化,new可以手动初始化。

3.malloc/free是函数,new/delete是操作符。

4.malloc申请空间时需要手动计算其空间大小,new申请空间编译器会自动计算,如果是多个对象new只需要在后面[]里加上对象个数即可。

5.malloc的返回值为void*,使用时必须将malloc强制转换,new不需要new后面就是类型。

6.malloc申请空间失败会返回空指针,而new申请空间失败会抛异常。

定位new

定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象。

使用格式:

new(place_address)type或者new(place_address)type(initializer-list)

place_address必须是一个指针。initializer-list是类型的初始化列表

使用场景:

定位new表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化,所以如果是自定义类型的对象,需要使用new的定义表达式进行显式调构造函数初始化。

a406f285455b49ed9b6c79424b1b6252.png

如上图,因为操作系统是给所有的资源分配空间所以当你去向操作系统申请空间的时候会慢一些,这个时候可以直接像自己的内存池要内存,内存池的内存也是从堆里来的,当内存池有内存的时候就会把空间给你,如果没有内存内存池就会去找操作系统,然后内存池找操作系统要内存,内存池要的内存一般比你申请的空间要大,因为内存池要保证内存够用并且下一次来申请还有足够的内存。内存池的方式相比找操作系统消耗会变小。下面是定位new的使用样例: 

class A
{
public:A(int a = 0): _a(a){cout << "A():" << this << endl;}~A(){cout << "~A():" << this << endl;}
private:int _a;
};
int main()
{A aa;A* p1 = (A*)malloc(sizeof(A));if (p1 == NULL){perror("malloc:");exit(-1);}//定位new 对已有的空间初始化。new(p1)A(1);A* p2 = new A;p1->~A();free(p1);delete p2;return 0;
}

图中我们用malloc开的空间是没办法初始化的,所以使用定位new对p1进行了初始化。p1既然既然是A*的指针那么就可以访问A类的公有函数,而这样的初始化其实我们用new就可以搞定,因为new对自定义类型会去调用构造函数。

内存泄漏的危害

什么是内存泄漏:内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内
存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对
该段内存的控制,因而造成了内存的浪费。
内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现
内存泄漏会导致响应越来越慢,最终卡死。
void MemoryLeaks()
{// 1.内存申请了忘记释放int* p1 = (int*)malloc(sizeof(int));int* p2 = new int;// 2.异常安全问题int* p3 = new int[10];Func(); // 这里Func函数抛异常导致 delete[] p3未执行,p3没被释放.delete[] p3;
}
C/C++程序中一般我们关心两种方面的内存泄漏:
堆内存泄漏(Heap leak)
堆内存指的是程序执行中依据须要分配通过malloc / calloc / realloc / new等从堆中分配的一
块内存,用完后必须通过调用相应的 free或者delete 删掉。假设程序的设计错误导致这部分
内存没有被释放,那么以后这部分空间将无法再被使用,就会产生Heap Leak。
系统资源泄漏
指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放
掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。
如何检测内存泄漏?
在vs下,可以使用windows操作系统提供的_CrtDumpMemoryLeaks() 函数进行简单检测,该
函数只报出了大概泄漏了多少个字节,没有其他更准确的位置信息。
int main()
{int* p = new int[10];// 将该函数放在main函数之后,每次程序退出的时候就会检测是否存在内存泄漏_CrtDumpMemoryLeaks();return 0;
}

那么怎么避免内存泄漏呢?

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

 


总结

c/c++的内存管理最大的区别在于如何处理自定义类型,由于c++要兼容c所以做了很多的取舍比如定位new等,本章的关键在于malloc/free与new/delete的区别并且正确匹配的使用new和malloc,只有养成良好的编码习惯才能减少内存泄漏的危害

 

相关文章:

c++动态内存分布以及和C语言的比较

文章目录 前言一.c/c内存分布 C语言的动态内存管理方式 C内存管理方式 operator new和operator delete函数 malloc/free和new/delete的区别 定位new 内存泄漏的危害总结前言 c是在c的基础上开发出来的&#xff0c;所以关于内存管理这一方面是兼容c的&…...

软考高级信息系统项目管理师系列之三十一:项目变更管理

软考高级信息系统项目管理师系列之三十一:项目变更管理 一、项目变更管理内容二、项目变更管理基本概念1.项目变更管理定义2.项目变更产生的原因3.项目变更的分类三、项目变更管理的原则和工作流程1.项目变更管理的原则2.变更管理的组织机构3.变更管理的工作程序四、项目变更管…...

【Vue3源码】第二章 effect功能的完善补充

【Vue3源码】第二章 effect功能的完善补充 前言 上一章节我们实现了effect函数的功能stop和onstop&#xff0c;这次来优化下stop功能。 优化stop功能 之前我们的单元测试中&#xff0c;stop已经可以成功停止了响应式更新&#xff08;清空了收集到的dep依赖&#xff09; st…...

CHAPTER 2 Web Server - apache(httpd)

Web Server - httpd2.1 http2.1.1 协议版本2.1.2 http报文2.1.3 web资源(web resource)2.1.4 一次完整的http请求处理过程2.1.5 接收请求的模型2.2 httpd配置2.2.1 MPM(多进程处理模块)1. 工作模式2. 切换MPM3. MPM参数配置2.2.2 主配置文件1. 基本配置2. 站点访问控制常见机制…...

【Vagrant】下载安装与基本操作

文章目录概述软件安装安装VirtualBox安装Vagrant配置环境用Vagrant创建一个VMVagrantfile文件配置常用命令概述 Vagrant是一个创建虚拟机的技术&#xff0c;是用来创建和管理虚拟机的工具&#xff0c;本身自己并不能创建管理虚拟机。创建和管理虚拟机必须依赖于其他的虚拟化技…...

常用类(五)System类

(1)System类常见方法和案例&#xff1a; &#xff08;1&#xff09;exit:退出当前程序 我们设计的代码如下所示&#xff1a; package com.ypl.System_;public class System_ {public static void main(String[] args) {//exit: 退出当前程序System.out.println("ok1"…...

Navicat Premium 安装 注册

Navicat Premium 一.Navicat Premium的安装 1.暂时关闭windows的病毒与威胁防护弄完再开&#xff0c;之后安装打开过程中弹窗所有警告全部允许,不然会被拦住 2.下载安装包&#xff0c;解压 链接&#xff1a;https://pan.baidu.com/s/1X24VPC4xq586YdsnasE5JA?pwdu4vi 提取码…...

回溯算法总结

首先回溯算法本身还是一个纯暴力的算法&#xff0c;只是回溯过程可能比较抽象&#xff0c;导致大家总是感觉看到的相关题目做的不是很顺畅&#xff0c;回溯算法一般来说解决的题目有以下几类&#xff1a;组合问题&#xff1a;lq77、lq17、lq39、lq40、lq216、切割问题&#xff…...

ccc-pytorch-基础操作(2)

文章目录1.类型判断isinstance2.Dimension实例3.Tensor常用操作4.索引和切片5.Tensor维度变换6.Broadcast自动扩展7.合并与分割8.基本运算9.统计属性10.高阶OP大伙都这么聪明&#xff0c;注释就只写最关键的咯1.类型判断isinstance 常见类型如下&#xff1a; a torch.randn(…...

独居老人一键式报警器

盾王居家养老一键式报警系统&#xff0c;居家养老一键式报警设备 &#xff0c;一键通紧急呼救设备&#xff0c;一键通紧急呼救系统&#xff0c;一键通紧急呼救器 &#xff0c;一键通紧急呼救终端&#xff0c;一键通紧急呼救主机终端产品简介&#xff1a; 老人呼叫系统主要应用于…...

软考案例分析题精选

试题一&#xff1a;阅读下列说明&#xff0c;回答问题1至问题4&#xff0c;将解答填入答题纸的对应栏内。某公司中标了一个软件开发项目&#xff0c;项目经理根据以往的经验估算了开发过程中各项任务需要的工期及预算成本&#xff0c;如下表所示&#xff1a;任务紧前任务工期PV…...

基于SpringBoot+vue的无偿献血后台管理系统

基于SpringBootvue的无偿献血后台管理系统 ✌全网粉丝20W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取项目下载方式&#x1f345; 一、项目背…...

详解js在事件中,如何传递复杂数据类型(数组,对象,函数)

文章目录 前言一、何谓预编译&#xff0c;变量提升&#xff1f;二、复杂数据类型的传递 1.数组2.对象3.函数总结前言 在JavaScript这门编程语言学习中&#xff0c;如何传参&#xff0c;什么是变量提升&#xff0c;js代码预编译等等。要想成为一名优秀的js高手&#xff0c;这些内…...

高并发架构 第一章大型网站数据演化——核心解释与说明。大型网站技术架构——核心原理与案例分析

大型网站架构烟花发展历程1.1.1初始阶段的网站构架1.1.2应用服务和数据服务分离1.1.3使用缓存改善网络性能1.1.4使用应用服务器集群改善网站的并发处理能力1.1.5数据库读写分离1.1.6使用反向代理和cdn加速网站相应1.1.1初始阶段的网站构架 大型网站都是由小型网站一步步发展而…...

VPP接口INPUT节点运行数据

在设置virtio接口接收/发送队列函数的最后&#xff0c;更新接口的运行数据。 void virtio_vring_set_rx_queues (vlib_main_t *vm, virtio_if_t *vif) { ...vnet_hw_if_update_runtime_data (vnm, vif->hw_if_index); } void virtio_vring_set_tx_queues (vlib_main_t *vm,…...

RabbitMQ学习(九):延迟队列

一、延迟队列概念延时队列中&#xff0c;队列内部是有序的&#xff0c;最重要的特性就体现在它的延时属性上&#xff0c;延时队列中的元素是希望 在指定时间到了以后或之前取出和处理。简单来说&#xff0c;延时队列就是用来存放需要在指定时间内被处理的 元素的队列。其实延迟…...

TCP并发服务器(多进程与多线程)

欢迎关注博主 Mindtechnist 或加入【Linux C/C/Python社区】一起探讨和分享Linux C/C/Python/Shell编程、机器人技术、机器学习、机器视觉、嵌入式AI相关领域的知识和技术。 TCP并发服务器&#xff08;多进程与多线程&#xff09;1. 多进程并发服务器&#xff08;1&#xff09;…...

第1章 Memcached 教程

Memcached是一个自由开源的&#xff0c;高性能&#xff0c;分布式内存对象缓存系统。 Memcached是以LiveJournal旗下Danga Interactive公司的Brad Fitzpatric为首开发的一款软件。现在已成为mixi、hatena、Facebook、Vox、LiveJournal等众多服务中提高Web应用扩展性的重要因素…...

【2022.12.9】Lammps+Python 在计算g6(r)时遇到的问题

目录写在前面绘制g6( r )执行步骤【updated】如何检查图像的正确性&#xff1a;不是编程问题&#xff0c;而是数学问题的一个小bug废稿2则&#xff1a;写在前面 全部log&#xff1a; 【2022.11.16】LammpsPythonMATLAB在绘制维诺图时遇到的问题 绘制g6( r )执行步骤【updated…...

MySQL使用C语言连接

文章目录MySQL使用C语言连接引入库下载库文件在项目中使用库使用库连接数据库下发SQL请求获取查询结果MySQL使用C语言连接 引入库 要使用C语言连接MySQL&#xff0c;需要使用MySQL官网提供的库。 下载库文件 下载库文件 首先&#xff0c;进入MySQL官网&#xff0c;选择DEVEL…...

浅谈 React Hooks

React Hooks 是 React 16.8 引入的一组 API&#xff0c;用于在函数组件中使用 state 和其他 React 特性&#xff08;例如生命周期方法、context 等&#xff09;。Hooks 通过简洁的函数接口&#xff0c;解决了状态与 UI 的高度解耦&#xff0c;通过函数式编程范式实现更灵活 Rea…...

云计算——弹性云计算器(ECS)

弹性云服务器&#xff1a;ECS 概述 云计算重构了ICT系统&#xff0c;云计算平台厂商推出使得厂家能够主要关注应用管理而非平台管理的云平台&#xff0c;包含如下主要概念。 ECS&#xff08;Elastic Cloud Server&#xff09;&#xff1a;即弹性云服务器&#xff0c;是云计算…...

【JavaEE】-- HTTP

1. HTTP是什么&#xff1f; HTTP&#xff08;全称为"超文本传输协议"&#xff09;是一种应用非常广泛的应用层协议&#xff0c;HTTP是基于TCP协议的一种应用层协议。 应用层协议&#xff1a;是计算机网络协议栈中最高层的协议&#xff0c;它定义了运行在不同主机上…...

8k长序列建模,蛋白质语言模型Prot42仅利用目标蛋白序列即可生成高亲和力结合剂

蛋白质结合剂&#xff08;如抗体、抑制肽&#xff09;在疾病诊断、成像分析及靶向药物递送等关键场景中发挥着不可替代的作用。传统上&#xff0c;高特异性蛋白质结合剂的开发高度依赖噬菌体展示、定向进化等实验技术&#xff0c;但这类方法普遍面临资源消耗巨大、研发周期冗长…...

关于nvm与node.js

1 安装nvm 安装过程中手动修改 nvm的安装路径&#xff0c; 以及修改 通过nvm安装node后正在使用的node的存放目录【这句话可能难以理解&#xff0c;但接着往下看你就了然了】 2 修改nvm中settings.txt文件配置 nvm安装成功后&#xff0c;通常在该文件中会出现以下配置&…...

Android15默认授权浮窗权限

我们经常有那种需求&#xff0c;客户需要定制的apk集成在ROM中&#xff0c;并且默认授予其【显示在其他应用的上层】权限&#xff0c;也就是我们常说的浮窗权限&#xff0c;那么我们就可以通过以下方法在wms、ams等系统服务的systemReady()方法中调用即可实现预置应用默认授权浮…...

中医有效性探讨

文章目录 西医是如何发展到以生物化学为药理基础的现代医学&#xff1f;传统医学奠基期&#xff08;远古 - 17 世纪&#xff09;近代医学转型期&#xff08;17 世纪 - 19 世纪末&#xff09;​现代医学成熟期&#xff08;20世纪至今&#xff09; 中医的源远流长和一脉相承远古至…...

论文阅读:LLM4Drive: A Survey of Large Language Models for Autonomous Driving

地址&#xff1a;LLM4Drive: A Survey of Large Language Models for Autonomous Driving 摘要翻译 自动驾驶技术作为推动交通和城市出行变革的催化剂&#xff0c;正从基于规则的系统向数据驱动策略转变。传统的模块化系统受限于级联模块间的累积误差和缺乏灵活性的预设规则。…...

Linux-进程间的通信

1、IPC&#xff1a; Inter Process Communication&#xff08;进程间通信&#xff09;&#xff1a; 由于每个进程在操作系统中有独立的地址空间&#xff0c;它们不能像线程那样直接访问彼此的内存&#xff0c;所以必须通过某种方式进行通信。 常见的 IPC 方式包括&#…...

Redis上篇--知识点总结

Redis上篇–解析 本文大部分知识整理自网上&#xff0c;在正文结束后都会附上参考地址。如果想要深入或者详细学习可以通过文末链接跳转学习。 1. 基本介绍 Redis 是一个开源的、高性能的 内存键值数据库&#xff0c;Redis 的键值对中的 key 就是字符串对象&#xff0c;而 val…...