第五站: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;
	
	//接收外部函数动态内存分配的地址 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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C# Cad2016二次开发选择文本信息导出(六)

//选文本信息导出 [CommandMethod("getdata")] public void getdata() {// 获取当前文档和数据库Document doc Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument;Database db doc.Database;Editor ed doc.Editor;// 获取当前…

亲手打造一个本地LLM语音助手来管理智能家居

经历过 Siri 和 Google 助手之后&#xff0c;我发现尽管它们能够控制各种设备&#xff0c;但却无法进行个性化定制&#xff0c;并且不可避免地依赖于云服务。出于对新知识的渴望以及想在生活中使用一些酷炫的东西&#xff0c;我下定决心&#xff0c;要追求更高的目标。我的要求…

我成为开源贡献者的原因竟然是做MySql-CDC数据同步

今年下半年机缘巧合下公司决定搭建自己的数据中台&#xff0c;中台的建设势必少不了数据集成。首先面临的就是数据集成技术选型的问题&#xff0c;按照社区活跃度、数据源适配性、同步效率等要求对市面上几个成熟度较高的开源引擎进行了深度调研。 最终经过内部讨论决定用Apac…

解决虚拟机字体太小的问题

在win11中&#xff0c;安装VMWare软件后&#xff0c;创建好虚拟机&#xff0c;打开终端后&#xff0c;发现终端里显示的字体太小&#xff0c;不方便使用&#xff0c;因此需要修改。 1、打开终端 2、输入"gsettings set org.gnome.desktop.interface text-scaling-factor…

运筹说 第98期|无约束极值问题

上一期我们一起学习了关于非线性规划问题的一维搜索方法的相关内容&#xff0c;本期小编将带大家学习非线性规划的无约束极值问题。 下面&#xff0c;让我们从实际问题出发&#xff0c;学习无约束极值问题吧&#xff01; 一、问题描述及求解原理 1 无约束极值问题的定义 无约…

日志采集传输框架之 Flume,将监听端口数据发送至Kafka

1、简介 Flume 是 Cloudera 提供的一个高可用的&#xff0c;高可靠的&#xff0c;分布式的海量日志采集、聚合和传 输的系统。Flume 基于流式架构&#xff0c;主要有以下几个部分组成。 主要组件介绍&#xff1a; 1&#xff09;、Flume Agent 是一个 JVM 进程&#xf…

SpringBoot异常处理(Whitelabel Error Page和自定义全局异常处理页面)和整合ajax异常处理

SpringBoot异常处理&#xff08;Whitelabel Error Page和自定义全局异常处理页面&#xff09;和整合ajax异常处理 1、springboot自带的异常处理页面Whitelabel Error Page SpringBoot默认的处理异常的机制&#xff1a;SpringBoot 默认的已经提供了一套处理异常的机制。一旦程…

继钱江之后,赛科龙也出自动挡?RA401自动挡曝光

QJ在发动赛921的当天&#xff0c;还有一台闪300搭载了自动挡&#xff0c;当天的热度高的离谱&#xff0c;并且后续也经常有人问&#xff0c;这自动挡啥时候上市等等&#xff0c;相信有很多人都想要一台排量大一点的自动挡摩托车&#xff0c;而最新的消息赛科龙也在开发一台&…

文本分类的一些记录

背景 过去工作中最常遇到的问题就是文本分类和实体抽取的任务。其中文本分类是自然语言处理中最基础的任务&#xff0c;指的是将文本打上特定的类别标签&#xff0c;以做区分和筛选。文本分类主要流程一般是&#xff1a;先预处理文本&#xff0c;再提取特征&#xff0c;最后通…

PDF修改技巧之:如何简单方便的编辑PDF文件?

在当今精通技术的世界中&#xff0c;PDF 的使用已变得普遍&#xff0c;尤其是在商业和教育方面。如果您在审阅 PDF 文件时遇到语法或其他错误怎么办&#xff1f; 尽管 PDF 文件不像 Word 或在线文档那样容易编辑&#xff0c;但借助高级工具&#xff0c;您一定可以进行编辑。 …

鸿蒙 ArkUI - 常用组件和布局

目录 一、组件 1.按钮 2.单选框 3.切换按钮 4.进度条 5.文本 6.文本输入框 二、布局方式 1.线性布局 2.层叠布局 3.弹性布局 4.网格布局 一、组件 ArkUI有丰富的内置组件&#xff0c;包括文本、按钮、图片、进度条、输入框、单选框、多选框等。我们还可以将基础组件…

HCIA基础知识

IP地址、静态路由、动态路由、交换机 OSPF RIP DHCP VLAN ACL NAT OSI TCP/IP UDP TCP 三次握手&#xff0c;四次挥手&#xff0c;报头 什么是网络&#xff1f; 由网络连接设备通过传输介质将网络终端设备连接起来&#xff0c;进行资源共享、信息传递的平台。 OSI七…

【电子取证篇】蘇小沐的电子取证工具合集在线文档

【电子取证篇】蘇小沐的电子取证工具合集在线文档 弄成了在线表格&#xff0c;记得及时保存&#xff1b;工具永远只是辅助&#xff0c;但不要过多依赖自动化&#xff0c;有难度说明可以提升&#xff0c;既要不断学习也要不停思考&#xff0c;知行合一—【蘇小沐】 【腾讯文档…

202405读书笔记|《作家榜名著:宋词三百首(马未都亲笔推荐版)》——绿酒初尝人易醉,一枕小窗浓睡

《作家榜名著&#xff1a;宋词三百首&#xff08;马未都亲笔推荐版&#xff09;》画很美&#xff0c;词也是&#x1f618;&#x1f618;&#xff0c;既廖远又色彩明艳&#xff0c;丰富而丰盈&#xff0c;看的很欢乐的一本书。部分节选如下&#xff1a; 艳溢香融 天遥地远&…

智能搬运机器人作为一种新型的物流技术

随着物流行业的快速发展&#xff0c;货物转运的效率和准确性成为了企业竞争的关键因素之一。智能搬运机器人作为一种新型的物流技术&#xff0c;已经在许多企业中得到了广泛应用。本文将介绍富唯智能智能搬运机器人在物流行业的应用和优势。 在实际应用中&#xff0c;智能搬运机…

如何在Eclipse IDE中安装TestNG插件

目录 使用Eclipse Marketplace安装TestNG插件 通过输入URL安装TestNG 1.点击安装新软件 2.输入URL以安装TestNG 3.遵循正常的安装过程 4.重新启动Eclipse 在Eclipse中安装TestNG插件的视频 在这篇文章中&#xff0c;我们将介绍如何在Eclipse IDE中安装TestNG插件&#x…

基于算术电路的全同态加密方案介绍

基于算术电路的全同态加密方案介绍 摘 要&#xff1a; 云计算技术目前已经发展得相对成熟&#xff0c;应用也逐步得到普及&#xff0c;它所具有的强大的数据处理能力&#xff0c;能够帮助个体用户计算复杂的数据。但它带来便利的同时&#xff0c;也催生了一系列用户隐私数据保…

网络安全全栈培训笔记(53-WEB攻防-通用漏洞CRLF注入URL重定向资源处理拒绝服务)

第53天 WEB攻防-通用漏洞&CRLF注入&URL重定向&资源处理拒绝服务 知识点&#xff1a; 1、CRLF注入-原理&检测&利用 2、URL重定向-原理&检测&利用 3、Web拒绝服务-原理&检测&利用 #下节预告&#xff1a; 1、JSONP&CORS跨域 2、域名安全…

HCIP ISIS实验

拓扑图&IP划分如下图&#xff1a; 第一步&#xff0c;配置IP地址&环回地址 以R1为例&#xff0c;R2~R8同理 interface GigabitEthernet 0/0/0 ip address 18.1.1.1 24 interface GigabitEthernet 0/0/1 ip address 12.1.1.1 24 interface LoopBack 0 ip address 1.1.…

代码训练营Day.34 | 1005. K次取反后最大化的数组和、134. 加油站、135. 分发糖果

1005. K次取反后最大化的数组和 1. LeetCode链接 1005. K 次取反后最大化的数组和 - 力扣&#xff08;LeetCode&#xff09; 2. 题目描述 3. 解法 整体来说&#xff0c;就是把负数全部取反&#xff0c;然后如果有剩余反转次数都给绝对值最小的数。 我的解法&#xff1a;先…