第五站:C++的内存解析

目录

C++内存分布

变量的四种存储方式

函数返回值使用指针(指针函数)

动态分配内存空间

不能使用外部函数的普通局部变量的地址

通过指针函数返回静态局部变量的地址

动态内存

根据需要分配内存,不浪费(根据用户的需求设置内存的容量)

被调用函数之外需要使用被调用函数内部的指针对应的地址空间

补充:指针函数和函数指针不同(详细例子请看第四站,函数指针):

突破栈区限制,可以给程序分配更多内存

动态内存的分配使用和释放

new 和 delete 运算符使用的一般格式为: 

内存泄漏

当忘记释放内存

 当开启释放内存

 内存检查工具

VS自带的

内存泄漏工具


C++内存分布

1、栈区(stack):由编译器自动分配释放,存放函数的参数值,局部变量值等。

2、堆区(heap):一般由程序员分配释放,随叫随到,挥之即走(动态内存)。

3、全局/静态区(static):全局变量和静态变量的存储是放在一起的,在程序编译时分配。

4、文字常量区:存放常量字符串

5、程序代码区:存放函数体(包括类的成员函数、全局函数)的二进制代码

 

变量的四种存储方式

分别为自动变量(auto)寄存器变量(register  . 静态变量 (static)、外部变量(extern))。

auto - 函数中所有的非静态的局部变量。(auto一般省略(c11以上不用写auto会报错,因为内部已经含有))

register - 一般经常被使用的的变量(如某一变量需要计算几千次)可以设 置成寄存器变量,register 变量会被存储在寄存器中,计算速度远快于存在内存 中的非 register 变量。

C++ 的 register 关键字已经优化,如果我们打印它的地址,它就变成了普通的 auto 变量

static - 在变量前加上 static 关键字的变量。

extern - 把全局变量在其他源文件中,声明成 extern 变量,可以扩展该全局变量的作用域至声明的那个文件,其本质作用就是对全局变量作用域的扩展。

#include <iostream>using namespace std;
//外部变量,可以用本文件其他源文件中的全局变量
extern int extern_a;//这里不能再给这个外部变量赋值//静态全局变量
static int c = 18;//寄存器变量
void register_demo() {register int i = 1;//寄存器变量本身没有地址// C++ 的 register 关键字已经优化,如果我们打印它的地址,它就变成了//普通的 auto 变量cout << "寄存器变量i的地址值:" << &i << endl;
}
//静态局部变量
void static_demo() {//静态变量,只会初始化一次,也就是这条语句只会执行一次,//被static的变量值,在函数体执行完后不会释放,下次执行改函数体,//这个变量可以用上一次执行函数体的值static int a = 18;int b = 18;a++;b++;c++;cout << "a作为静态局部变量的值:" << a << endl;cout << "c作为静态全局变量的值:" << c << endl;cout << "b作为局部变量auto的值:" << b << endl;
}
void extern_demo() {extern_a++;cout << "外部变量的值:" << extern_a << endl;
}
int main(void) {int i = 18;//c语言可以写上auto也不会报错,但是c++做了升级写了auto会报错cout << "局部变量的值:" << i << endl;cout << endl;register_demo();cout << endl;for (int i = 0; i < 3; i++) {static_demo();cout << endl;}cout << endl;extern_demo();
}

函数返回值使用指针(指针函数)

可以返回函数内部:动态分配内存地址 局部静态变量地址 以及全局静态变量和外部变量 地址

动态分配内存空间

#include <iostream>
#include <stdlib.h>
using namespace std;//返回动态内存分配地址
int* add1(int x, int y)
{int* sum = NULL;sum = new int;*sum = x + y;return sum;
}
int main()
{int a = 3, b = 5;int* sum = NULL;//接收外部函数动态内存分配的地址 oksum = add1(a, b);cout<<*sum<<endl;delete sum;system("pause");return 0;
}

不能使用外部函数的普通局部变量的地址

(普通局部变量的值在函数调用结束后值会释放)

(这样是错误的 vs版本升级后,这种写法会触发断点,但是以前的不会),但是依然会运行出结果,这种结果会被栈空间覆盖掉(如果后面有其他函数用到这片空间)那么这个函数所运行的结果也会被后来的函数值覆盖掉,运行出来的值并不正确

#include <iostream>
#include <stdlib.h>
using namespace std;int* add(int x, int y) {int sum = x + y;return &sum;
}//程序动态动态分配一块内存空间,这片空间会覆盖在上面函数在调用结束后,释放的空间之上
int* add1(int x, int y)
{int* sum = NULL;sum = new int;*sum = x + y;return sum;
}
int main()
{int a = 3, b = 5;int* sum = NULL;//不能使用外部函数局部变量的地址(这样是错误的)sum = add(a, b);//如果再输出这个外部函数局部变量的值之前,调用一片由程序员动态分配内存的指针函数,那么这片申    //请的内存空间会覆盖掉上面函数的之前使用的空间,add1(a,b);cout<<*sum<<endl;//这里输出的值就会受到上面这个函数的影响从而返回错误的值delete sum;system("pause");return 0;
}

通过指针函数返回静态局部变量的地址

:这种方法是可以的,不同于上面这种普通局部变量,静态变量的特点:(只会初始化一次,但是当函数调用结束后,运行出来的值并不会释放)

#include <iostream>
#include <stdlib.h>
using namespace std;//通过指针函数返回静态局部变量的地址
int* add(int x, int y)
{static int sum = 0;//静态变量的值不会因为函数调用结束而释放cout << "函数内部的值:" << sum << endl;sum = x + y;return &sum;
}int main()
{int a = 3, b = 5;int* sum = NULL;sum = add(a, b);cout << "第一次调用静态后的函数值:" << *sum << endl;*sum = 8888;sum = add(a, b);cout << "第二次调用静态后的函数值:" << *sum << endl;return 0;
}

动态内存

根据需要分配内存,不浪费(根据用户的需求设置内存的容量)

#include <iostream>using namespace std;int main(void) {int a[] = { 11,2,34,12,18,19,17,10 };int len = sizeof(a) / sizeof(a[0]);int num = 0;//1.按需分配,根据需要分配内存,不浪费int* salary = NULL;cout << "请输入内存数:" << endl;cin >> num;//判断输入的数是否大于数组的长度,不大于则输入不合法//需要按需分配内存,就得大于本来数组的长度if (num < len) {cout << "输入数字不合法!.." << endl;}//第一种:使用指针形式逐个赋值salary = new int[num];for (int i = 0; i < len; i++) {*(salary + i) = a[i];}//将多出a数组的值,赋值为18for (int i = len; i < num; i++){*(salary + i) = 18;}for (int i = 0; i < num; i++) {cout << "输出第" << i + 1 << "的值为:" << *(salary + i)<<endl;}cout << endl;//第二种:使用内存拷贝函数,c提供//从源 a 所指的内存地址的起始位置开始拷贝 len 个字节到目标 salary 所指的//内存地址的起始位置中memcpy(salary, a, len);for (int i = len; i < num; i++) {*(salary + i) = 18;}for (int i = 0; i < num; i++) {cout << "输出第" << i + 1 << "的值为:" << *(salary + i)<<endl;}delete[] salary;
}

 void *memcpy(void *dest, const void *src, size_t n); #include <string>

功能:从源 src 所指的内存地址的起始位置开始拷贝 n 个字节到目标 dest 所指的 内存地址的起始位置中

被调用函数之外需要使用被调用函数内部的指针对应的地址空间

#include <iostream>using namespace std;
/*
2.被调用函数之外需要使用被调用函数内部的指针对应的地址空间*/
//指针函数返回动态内存
int* demo(int count) {int* pointer = NULL;//通过c++的方式申请内存空间pointer = new int[count];//通过c语言的方式申请内存空间//pointer = (int*)malloc(sizeof(int) * count);for (int i = 0; i < count; i++) {*(pointer + i) = 100 + i;}for (int i = 0; i < count; i++) {cout << "通过指针函数返回的值" << *(pointer + i) << endl;}return pointer;
}
//通过二级指针
void demo1(int count, int** pointer1) {int* ap = NULL;*pointer1 = new int[count];ap = *pointer1;for (int i = 0; i < count; i++) {*(ap + i) = 100 + i;}for (int i = 0; i < count; i++) {cout << "通过二级指针返回的值" << *(ap + i) << endl;}}
int main(void) {int* pointer1 = NULL;int count = 10;//cout << "通过指针函数调用返回的值:" << endl;//pointer1 = demo(count);//cout << endl;//for (int i = 0; i < count; i++){//	cout << "通过指针函数调用返回的值:" << *(pointer1 + i) << endl;//}cout << "通过二级指针调用返回的值:" << endl;demo1(count, &pointer1);cout << endl;for (int i = 0; i < count; i++) {cout << "通过二级指针调用返回的值:" << *(pointer1 + i) << endl;}delete pointer1;//要释放内存return 0;
}

补充:指针函数和函数指针不同(详细例子请看第四站,函数指针):

函数指针是指向函数的指针变量,用于存储和调用函数。
指针函数是返回值为指针类型的函数,用于返回不同的指针值或函数指针。
函数指针可以作为一个指针类型,成为指针函数的类型,

突破栈区限制,可以给程序分配更多内存

函数分配的栈空间大小一般都有限制,Windows一般为1-2M,但是可以使用动态内存分配,给函数分配更多的内存空间(也不能太大,程序员动态分配的内存空间大小一般为2个G左右,也就是堆空间大小为2G左右)一个函数可供分配就1G左右,当写到第三的时候就会出现运行出错

#include <iostream>using namespace std;
/*
3.突破栈区限制,可以给程序分配更多内存*/
void demo() {int* ap = NULL;ap = new int[1024 * 1024*1000*1.02];//1个G是能分配的,但是当到达1.03G就会报错
}
void demo1() {int* ap = NULL;ap = new int[1024 * 1024 * 1000 * 1.02];//1个G是能分配的,但是当到达1.03G就会报错}int main(void) {demo();demo1();return 0;
}

 当分配三个时候:程序报错bad_alloc,这个就是动态内存溢出的意思

动态内存的分配使用和释放

C 语言中是利用库函数 malloc 和 free 来 分配和撤销内存空间的。C++提供了较简便而功能较强的运算符 new 和 delete 来 取代 malloc 和 free 函数。(支持互相混合使用的)

(注意: new 和 delete 是运算符,不是函数,因此执行效率高。)

new 和 delete 运算符使用的一般格式为: 

 new 运算符 动态分配堆内存 使用方法:

指针变量 = new 类型(常量);//常量可缺省

指针变量 = new 类型[表达式]; //数组

指针变量 = new 类型[表达式][表达式] //二维数组

delete 运算符 释放已分配的内存空间 使用方式:其中“指针变量” 必须时一个 new 返回的指针!

普通类型(非数组)使用: delete 指针变量;

数组 使用: delete[] 指针变量;

#include <iostream>using namespace std;int main(void) {//第一种分配动态内存不执行初始化int* p1 = new int;*p1 = 100;//第二种分配动态内存同时执行初始化int* p2 = new int(100);// 第三种 malloc 返回值是 void *int* p3 = (int*)malloc(sizeof(int));free(p1); //基础类型可以 new free 可以混搭delete p3; //基础类型可以 malloc delete 可以混搭delete p2; //free(p2); 同样效果int **p4 = new int*[1];free(p4);return 0;
}

内存泄漏

在企业开发中,一个项目往往需要一天24小时不断运行,如果忘记内存释放,程序内存就会一直运行,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。

当忘记释放内存

#include <iostream>
#include <Windows.h>
using namespace std;//用来记录没有释放内存的
void demo() {int* p1 = NULL;p1 = new int[1024*10];p1[0] = 1;
}
int main(void) {for (int i = 0; i < 102400; i++){//便于观察内存泄漏情况demo();Sleep(5);}return 0;
}

 当开启释放内存

#include <iostream>
#include <Windows.h>
using namespace std;void demo1() {int* p1 = NULL;p1 = new int[1024*10];p1[0] = 0;delete[] p1;
}
int main(void) {for (int i = 0; i < 102400; i++){//便于观察内存泄漏情况demo1();Sleep(5);}return 0;
}

 内存检查工具

VS自带的

VisualC++ debugger 和 CRT 库

第一步: 包含以下头文件 :debug需要在debug模式下调试才能看到内存泄漏的信息

#define _CRTDBG_MAP_ALLOC

#include <stdlib.h>
#include <crtdbg.h>

第二步: 接管 new 操作符

#ifdef _DEBUG

#ifndef DBG_NEW

#define DBG_NEW new ( _NORMAL_BLOCK , __FILE__ ,__LINE__)

#define new DBG_NEW

#endif

#endif

第三步: 在代码结束出输出内存泄漏信息

_CrtDumpMemoryLeaks();

#include <iostream>
#include <Windows.h>
#include <stdlib.h>
#include <crtdbg.h>
#define _CRTDBG_MAP_ALLOC_
using namespace std;#ifdef _DEBUG
#ifndef DBG_NEW
#define DBG_NEW new ( _NORMAL_BLOCK , __FILE__ ,__LINE__)
#define new DBG_NEW
#endif
#endif
//用来记录没有释放内存的
void demo() {int* p1 = NULL;p1 = new int[1024 * 100];p1[0] = 1;
}
int main(void) {for (int i = 0; i < 5; i++) {//便于观察内存泄漏情况demo();Sleep(5);}_CrtDumpMemoryLeaks();return 0;
}

内存泄漏工具

内存泄漏工具: Windows : Purify,BoundsCheaker、Deleaker、VisualLeak Detector(VLD), Linux 平台:Valgrind memcheck

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

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

相关文章

2024年1月11日 主题:非枪人生

2024年1月11日15:58:29 2024年1月11日15:35:13 2024年1月11日15:57:51 对物理进行大致预 2024年1月11日20:27:14 结论&#xff1a;不适合进行数据结构的训练和对电路的模拟感受 2024年1月11日20:28:32 今天也平静的结束了 不需要键盘的支持也就这么结束了我也不知道…

应用在LCD显示器电源插头里的氮化镓(GaN)MTC-65W1C

LCD&#xff08;Liquid Crystal Display&#xff09;显示器是利用液晶显示技术来进行图像表现的显示装置&#xff0c;从液晶显示器的结构来看&#xff0c;无论是笔记本电脑还是桌面系统&#xff0c;采用的LCD显示屏都是由不同部分组成的分层结构。LCD显示器按照控制方式不同可分…

适配 IOS 安全区域

安全区域指的是一个可视窗口范围&#xff0c;处于安全区域的内容不受圆角&#xff08;corners&#xff09;、齐刘海&#xff08;sensor housing&#xff09;、小黑条&#xff08;Home Indicator&#xff09;影响。 造成这个问题的主要原因就是 iphoneX 之后在屏幕上出现了所谓…

实现STM32烧写程序-(1)获取Bootloader版本信息

简介 如何像ST Flash Loader等工具一样写自己的烧写程序呢?文档 AN3155: USART protocol used in the STM32 bootloader 步骤 Boot模式 将 开发板例如STM32F103C8T6 Boot0->1 & Boo1->0 重启或复位进入系统存储模式 物理连接 将USART1 通过 USB转TTL线连接到…

NLP(十八):LLM 的推理优化技术纵览

原文&#xff1a;NLP&#xff08;十八&#xff09;&#xff1a;LLM 的推理优化技术纵览 - 知乎 目录 收起 一、子图融合&#xff08;subgraph fusion&#xff09; 1.1 FasterTransformer by NVIDIA 1.2 DeepSpeed Inference by Microsoft 1.3 MLC LLM by TVM 二、模型压…

Windows7共享文档—开启方法及用户权限设置

使用计算机的朋友&#xff0c;在工作中经常需要在局域网中将文件共享给其他用户&#xff0c;这样其他人可以方便的通过局域网查看&#xff0c;甚至修改这些共享文件。当然&#xff0c;根据文件的重要程度&#xff0c;使用等级不同&#xff0c;不同的用户会赋予不同的权限&#…

PPT插件-大珩助手-《提取选中的幻灯片》-选中新建

选中新建 提取选中的幻灯片到新的幻灯文稿中。PDF编辑器可以提取指定的页面到新的PDF文档中&#xff0c;PPT没有这个功能&#xff0c;因此开发。 软件介绍 PPT大珩助手是一款全新设计的Office PPT插件&#xff0c;它是一款功能强大且实用的PPT辅助工具&#xff0c;支持Wps Wo…

js(JavaScript)数据结构之字典

什么是数据结构&#xff1f; 下面是维基百科的解释&#xff1a; 数据结构是计算机存储、组织数据的方式。数据结构意味着接口或封装&#xff1a;一个数据结构可被视为两个函数之间的接口&#xff0c;或者是由数据类型联合组成的存储内容的访问方法封装。 我们每天的编码中都会…

K8S的dashboard使用账号密码登录

原文网址&#xff1a;K8S的dashboard使用账号密码登录-CSDN博客 简介 本文介绍K8S的dashboard使用账号密码登录的方法。 ----------------------------------------------------------------------------------------------- 分享Java真实高频面试题&#xff0c;吊打面试官&…

uniapp中uview组件库丰富的ActionSheet 操作菜单使用方法

目录 #平台差异说明 #基本使用 #配置顶部的提示信息和底部取消按钮 #如何知道点了第几项 #API #Props #Event 本组件用于从底部弹出一个操作菜单&#xff0c;供用户选择并返回结果。 本组件功能类似于uni的uni.showActionSheetAPI&#xff0c;配置更加灵活&#xff0c;所…

使用知行之桥EDI系统的HTTP签名身份验证

本文简要概述了如何在知行之桥EDI系统中使用 HTTP 签名身份验证&#xff0c;并将使用 CyberSource 作为该集成的示例。 API 概述 API 是”应用编程接口”的缩写。这听起来可能很复杂&#xff0c;但它的真正含义是一种允许两个不同实体相互通信的软件。自开发以来&#xff0c;…

php内置函数-文件包含的函数

目录 1.include 2.require 3.include_once 4. require_once 1.include 可以将别的文件直接引用过来&#xff08;被引用的文件含有打印代码的话&#xff0c;会直接打印&#xff09;&#xff0c;如果失败了&#xff0c;会返回一条警告&#xff0c;文件会继续执行下去&#…

Skywalking UI页面中操作的各种实用功能汇总

刚刚接触skywalking不久&#xff0c;在这里总结一下在UI页面中操作的各种实用功能&#xff0c;随着使用的不断深入&#xff0c;我也会对文章进行持续补充。 本文skywalking 的ui入口是官方demo &#xff0c;版本是10.0.0-SNAPSHOT-593bd05 http://demo.skywalking.apache.org…

网络安全B模块(笔记详解)- 隐藏信息探索

隐藏信息探索 1.访问服务器的FTP服务,下载图片QR,从图片中获取flag,并将flag提交; ​ 通过windows电脑自带的图片编辑工具画图将打乱的二维码分割成四个部分,然后将四个部分通过旋转、移动拼接成正确的二维码 ​ 使用二维码扫描工具CQR.exe扫描该二维码 ​ 获得一串…

性能分析与调优: Linux 磁盘I/O 观测工具

目录 一、实验 1.环境 2.iostat 3.sar 4.pidstat 5.perf 6. biolatency 7. biosnoop 8.iotop、biotop 9.blktrace 10.bpftrace 11.smartctl 二、问题 1.如何查看PSI数据 2.iotop如何安装 3.smartctl如何使用 一、实验 1.环境 &#xff08;1&#xff09;主机 …

CentOS安装k8s单机/集群及一些命令

目录 前言 1. 安装docker 2. 安装要求 3.准备网络&#xff08;如果只装单机版可跳过此部&#xff09; 4. 准备工作 5. 安装 5.1. 配置阿里云yum k8s源 5.2 安装kubeadm、kubectl和kubelet 5.3 初始化&#xff0c;只在master执行&#xff0c;子节点不要执行 5.3.1 一些…

npm报错error:03000086:digital envelope routines::initialization error

1.可能是因为node版本过高&#xff0c;与现在的项目不符合 这是降低node版本的命令&#xff0c;然后重新运行 npm install npm8.1.2 -g 2.改下这个package.json "dev": "SET NODE_OPTIONS--openssl-legacy-provider && vue-cli-service serve",也…

编码器与解码器LLM全解析:掌握NLP核心技术的关键!

让我们深入了解&#xff1a;基于编码器和基于解码器的模型有什么区别&#xff1f; 编码器与解码器风格的Transformer 从根本上说&#xff0c;编码器和解码器风格的架构都使用相同的自注意力层来编码词汇标记。然而&#xff0c;主要区别在于编码器旨在学习可以用于各种预测建模…

【Android开发】不同Activity之间的数据回传实例(一)摘桃子游戏

一、功能介绍 该项目实现的功能主要有&#xff1a; 在首页显示一个按钮点击该按钮跳转到桃园页面在桃园页面&#xff0c;点击桃子会弹窗显示摘到几个桃子&#xff0c;同时被点击桃子消失&#xff0c;总桃子数1点击退出桃园会返回首页&#xff0c;首页桃子数会根据点击的桃子数…

数据结构与算法教程,数据结构C语言版教程!(第三部分、栈(Stack)和队列(Queue)详解)四

第三部分、栈(Stack)和队列(Queue)详解 栈和队列&#xff0c;严格意义上来说&#xff0c;也属于线性表&#xff0c;因为它们也都用于存储逻辑关系为 "一对一" 的数据&#xff0c;但由于它们比较特殊&#xff0c;因此将其单独作为一章&#xff0c;做重点讲解。 使用栈…