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

发布时间:2024年01月11日

目录

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

文章来源:https://blog.csdn.net/qq_48397625/article/details/135464260
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。