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

news2024/12/24 9:00:14

目录

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;
	
	//接收外部函数动态内存分配的地址 ok
	sum = 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数组的值,赋值为18
	for (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.coloradmin.cn/o/1374197.html

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

相关文章

柳胜勋:0.88秒!NineData数据库编程大赛的第二名,轻松完成百万级数据代码评测!!

数据库编程大赛&#xff1a;一条SQL计算扑克牌24点 12月27日&#xff0c;NineData和云数据库技术社区主办&#xff0c;华为云、火山引擎、开源中国、云和恩墨、TDengine、云猿生数据、DORIS、ITPUB等协办单位和媒体&#xff0c;共同举办了本次《数据库编程大赛》。大赛题目「用…

LeetCode-657/1275/1041

1.机器人能否返回原点&#xff08;657&#xff09; 题目描述&#xff1a; 在二维平面上&#xff0c;有一个机器人从原点 (0, 0) 开始。给出它的移动顺序&#xff0c;判断这个机器人在完成移动后是否在 (0, 0) 处结束。 移动顺序由字符串 moves 表示。字符 move[i] 表示其第 …

Python办公自动化 – 自动化文本翻译和Oracle数据库操作

Python办公自动化 – 自动化文本翻译和Oracle数据库操作 以下是往期的文章目录&#xff0c;需要可以查看哦。 Python办公自动化 – Excel和Word的操作运用 Python办公自动化 – Python发送电子邮件和Outlook的集成 Python办公自动化 – 对PDF文档和PPT文档的处理 Python办公自…

python统计分析——箱线图(df.boxplot)

资料来源&#xff1a;用python学统计学&#xff0c;帮助文档 使用pd.dataframe.boxplot()函数绘制箱线图 import numpy as np import pandas as pd from matplotlib import pyplot as pltdfpd.DataFrame({type:[A,A,A,A,A,A,A,A,A,A,B,B,B,B,B,B,B,B,B,B],value:[2,3,3,4,4,4…

门店管理系统驱动智慧零售升级

在当今数字化经济的大潮中&#xff0c;实体门店正在经历一场由内而外的深度变革。门店管理系统以其高效、便捷和全面的功能特性&#xff0c;为实体店提供了高效的运营解决方案。 门店管理系统拜托了传统零售业对本地化软件的依赖&#xff0c;它将复杂的信息技术转化为易于获取…

Redis性能大挑战:深入剖析缓存抖动现象及有效应对的战术指南

在实际应用中&#xff0c;你是否遇到过这样的情况&#xff0c;本来Redis运行的好好的&#xff0c;响应也挺正常&#xff0c;但突然就变慢了&#xff0c;响应时间增加了&#xff0c;这不仅会影响用户体验&#xff0c;还会牵连其他系统。 那如何排查Redis变慢的情况呢&#xff1f…

隧道自动化监测系统的主要产品和监测内容

一、背景 随着交通行业的不断发展&#xff0c;隧道作为交通基础设施的重要组成部分&#xff0c;其安全和稳定性对于保障人们的生命财产安全具有重要意义。隧道自动化监测系统作为一种先进的安全监测手段&#xff0c;能够实时监测隧道内部的各项参数&#xff0c;为隧道的安全运…

Kubernetes的动态pv

pv和pvc存储卷 存储卷: emptyDir:容器内部&#xff0c;随着pod销毁&#xff0c;emptyDir也会消失&#xff0c;不能做数据持久化 hostPath:持久化存储数据可以和节点上目录做挂载&#xff0c;pod被销毁了数据还在 NfS&#xff1a;一台机器&#xff0c;提供pod内容器所有的挂…

Elasticsearch 索引文档时create、index、update的区别【学习记录】

本文基于elasticsearch7.3.0版本。 一、思维导图 elasticsearch中create、index、update都可以实现插入功能&#xff0c;但是实现原理并不相同。 二、验证index和create 由上面思维导图可以清晰的看出create、index的大致区别&#xff0c;下面我们来验证下思维导图中的场景&…

2023年山东省职业院校技能大赛高职组信息安全管理与评估 理论题(正式赛)

2023年山东省职业院校技能大赛高职组信息安全管理与评估 理论题 理论技能与职业素养&#xff08;100分&#xff09; 2023年山东省职业院校技能大赛高职组信息安全管理与评估 理论题 【注意事项】 Geek极安云科专注技能竞赛技术提升&#xff0c;基于各大赛项提供全面的系统性…

大数据系列之:腾讯云服务器性能和价格比较

大数据系列之&#xff1a;腾讯云服务器性能和价格比较 一、磁盘性能和价格比较二、高性能云硬盘三、ssd云硬盘四、极速型ssd云硬盘五、增强型ssd云硬盘六、查看腾讯云服务器价格 一、磁盘性能和价格比较 磁盘名称高性能ssd云硬盘极速型ssd云硬盘增强型ssd云硬盘规格500g 5800 …

九州金榜|提高孩子认知,让问题自动消失

哈佛大学教授桑斯坦曾提出一个概念&#xff0c;叫做“信息茧房”。 意思是说&#xff1a;人们关注的信息领域&#xff0c;会习惯性地被自己的思想所引导&#xff0c;当你长时间接受单一观点时&#xff0c;就像蚕茧一样把自己越包越紧&#xff0c;越来越封闭。 走不出认知逻辑…

爬虫01-爬虫原理以及爬虫前期准备工作

文章目录 1 爬虫基本原理什么是爬虫爬虫功能详解爬虫基本流程两个概念&#xff1a;request和response 2 一些问题爬虫能抓取什么样的数据&#xff1f;抓取的数据怎么提取部分内容&#xff1f;数据解析方式。为什么我爬虫抓取的数据和浏览器看到的不一样怎样解决JavaScript渲染的…

数字档案安全与高效管理的先锋——亚信安慧AntDB数据库

档案工作在维护历史真实面貌、保障人民利益方面具有至关重要的作用。随着社会的发展&#xff0c;数字化转型成为档案管理领域的不可逆趋势。数字档案的存储和传输已经成为档案工作的重要组成部分&#xff0c;然而&#xff0c;这也伴随着一系列的挑战&#xff0c;其中安全风险是…

window11后台服务优化记录

这里:\WINDOWS\xxx\svchost.exe -k netsvcs -p 信号聚合器服务&#xff0c;用于根据时间、网络、地理位置、蓝牙和 CDF 因素评估信号。支持的功能包括设备解锁、动态锁定和动态 MDM 策略 参考&#xff1a; 优化参考v1

时间差异导致数据缺失,如何调整Grafana时间与Prometheus保持同步?

Grafana时间如何调快或调慢&#xff1f; 在k8s环境中&#xff0c;常使用prometheusgrafana做监控组件&#xff0c;prometheus负责采集、存储数据&#xff0c;grafana负责监控数据的可视化。 在实际的使用中&#xff0c;有时会遇到这样的问题&#xff0c;k8s集群中的时间比真实…

Zabbix6.4 监控系统 密码忘记怎么办

Zabbix6.4 监控系统 密码忘记怎么办&#xff1f; 如下图 本次主要介绍在Zabbix6.4中重置用户密码的步骤。 步骤 如果您忘记了Zabbix密码并且无法登录&#xff0c;请向Zabbix管理员求助。 超级管理员用户可以在用户配置表单中更改所有用户的密码。 如果超级用户忘记了密码&a…

C++指针小练习

双色球统计1-33个数字出现的次数(很详细) 做这个题一定要注意审题:题目要求是统计1-33个数字出现的次数,而不是前六个数字出现的次数 算法设计: ①:用一个数组p1来保存每一行的数据,再用一个数组p2来遍历1-33个数字,因为是要统计这33个数字出现的次数所以将数组初始化为0, ②…

2023年全国职业院校技能大赛(高职组)“云计算应用”赛项赛卷⑦

2023年全国职业院校技能大赛&#xff08;高职组&#xff09; “云计算应用”赛项赛卷7 目录 需要竞赛软件包环境以及备赛资源可私信博主&#xff01;&#xff01;&#xff01; 2023年全国职业院校技能大赛&#xff08;高职组&#xff09; “云计算应用”赛项赛卷7 模块一 …

建模软件Rhinoceros mac介绍说明

Rhinoceros mac是一款3D设计软件“犀牛”&#xff0c;在当今众多三维建模软件中&#xff0c;Rhinoceros 版因为其体积小、功能强大、对硬件要求低而广受欢迎&#xff0c;对于专业的3D设计人员来说它是一款不错的3D建模软件&#xff0c;Rhinoceros Mac中文版能轻易整合3DS MAX与…