动态内存管理(malloc)

本文深入探讨了动态内存管理,包括malloc、calloc、realloc和free的基本用法及注意事项。讲解了堆和栈的区别,动态内存分配函数的工作原理,以及动态开辟二维数组的方法。强调了内存碎片、内存泄露问题及其防范措施,并通过实例展示了如何避免常见的编程错误。
摘要由CSDN通过智能技术生成

申请大块空间使用malloc
申请小块空间使用内存池

内存池由用户自己进行管理,不用系统进行malloc和free;因为系统进行malloc和free时耗时大,当申请空间过小,额外空间所占就大

因为malloc需要引入头部信息,当申请的空间过小头部信息的比重就大于了数据信息,浪费严重且效率不高

动态内存管理的原因:

  • 栈区空间大小不够
  • 有限的空间,最大的利用

堆和栈的区别

堆和栈的区别

(一)初识动态内存

Windows 系统下,栈空间(.stack)分配1M
Linux系统下栈空间分配10M(16G内存);
在这里插入图片描述

int main()//测试堆区大小
{
	int n = 1024 * 1024 * 100;//100M
	char* p = nullptr;
	int sum = 0;
	for (;;)
	{
		p = (char*)malloc(sizeof(char) * n);
		if (p != nullptr)
		{
			sum += 1;
		}
		else
		{
			break;
		}
	}
	printf("%d \n", sum);
	return 0;

}

(二)动态内存分配函数

在这里插入图片描述
前3个函数都用来申请空间,申请的空间都由free释放
calloc与realloc 底层都有调用malloc

malloc

malloc : 向堆区申请一块指定大小的连续内存空间,用cdcd…填充
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

  • malloc 从堆区申请空间,成功申请并不进行初始化
  • size 是用户从堆区申请空间的个数(多少字节)
  • malloc申请的是字节的个数;
  • 返回值是一个无类型指针,故对其返回值需要强转;
  • 内存分配成功返回其申请到的堆区地址,若未成功返回NULL,因此每次申请完必须判空
  • 使用完成必须用free来释放,free释放的并不是指针本身,而是将指针指向的堆区空间释放
int main()
{
	int n = 0;
	int i = 0;
	int* ip = nullptr;
	scanf_s("%d", &n);

//int ar[n];//如果系统支持C99,可动态开辟数组,但是数组在栈区开辟
	//当n超过1M会栈溢出

	ip = (int*)malloc(sizeof(int) * n);
	if (nullptr == ip)exit(1);
	for (int i = 0;i < n;++i)
	{
		ip[i] = i+10;
	}

	for (int i = 0;i < n;++i)
	{
		printf("%2d", ip[i]);
	}

	free(ip);//此时ip为空悬指针(失效指针)
	ip = nullptr;

	return 0;
}

在这里插入图片描述

malloc申请空间图示:

在这里插入图片描述

极端情况malloc(0)

malloc(0)并不意味着返回nullptr
辅助信息所占的比重是100%
在这里插入图片描述

int main()
{
	int *ip= (int*)malloc(0);

	free(ip);

	return 0;
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

malloc与指针

改错题:
在这里插入图片描述

在这里插入图片描述
改进一:引用

void GetString(char*&p, int n)
{
	p = (char*)malloc(sizeof(char) * n);
	if (p == nullptr) exit(EXIT_FAILURE);
}
int main()
{
	int n = 100;
	char* cp = NULL;

	GetString(cp, n);
	strcpy_s(cp, n, "hello tulun");
	printf("%s", cp);
	free(cp);
	return 0;
}

改进二:返回指针

char* GetString(char*p, int n)
{
	p = (char*)malloc(sizeof(char) * n);
	if (p == nullptr) exit(EXIT_FAILURE);
	return p;

}
int main()
{
	int n = 100;
	char* cp = NULL;
	
	cp = GetString(cp, n);
	strcpy_s(cp,n, "hello tulun");
	printf("%s \n", cp);
	free(cp);
	return 0;
}

改进三:二级指针

void GetString(char**p, int n)
{
	*p = (char*)malloc(sizeof(char) * n);
	if (p == nullptr) exit(EXIT_FAILURE);
}
int main()
{
	int n = 100;
	char* cp = NULL;
	
	GetString(&cp, n);
	strcpy_s(cp,n, "hello tulun");
	printf("%s", cp);
	free(cp);
	return 0;
}

calloc

calloc : 向堆区申请一块指定大小的连续内存空间,用000…填充
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

int main()
{
	int n = 5;
	int *ip= (int*)malloc(sizeof(int)*n);
	free(ip);
	ip = nullptr;

	int* is = (int*)calloc(n, sizeof(int));
	free(is);
	is = nullptr;
	return 0;
}

ip指向的空间:
在这里插入图片描述
is指向的空间:
在这里插入图片描述
在这里插入图片描述
memset / memcpy () 在 <string.h>
<string.h>等价于《cstring》是C的字符函数库,里面封装有strcmp,strcpy,strlen;memset,memcpy…
《string》是C++的字符串库

在这里插入图片描述
自己写一个my_calloc函数模拟calloc:

//              元素个数    每个元素大小
void* my_calloc(size_t num, size_t size)
{
	void* vp = malloc(size * num);
	if (nullptr != vp)
	{
		memset(vp, 0, num * size);//将vp指向的空间全部置为0;
	}

	return vp;
}

realloc

动态内存扩容或者收缩,realloc函数在内存空间足够时直接追加;内存空间不足时,重新选堆空间重新申请到足够内存空间,并且释放原空间;空间不足且无法申请到指定大小空间直接返回NULL,且原空间不会释放。所以写代码时应该避免这种情况发生,避免内存泄露。当使用realloc()收缩空间时一定会成功,所以不需要在意是否会返回NULL,但是收缩空间会造成内存碎片
在这里插入图片描述

  • ptr: 指向已经存在的一块动态内存区域,没有被free 过
  • new_size: 需要扩张或者收缩的大小
    在这里插入图片描述
    在这里插入图片描述
#include<stdio.h>//标准解析代码
#include<stdlib.h>  // _itoa_s malloc  free
#include<assert.h>
#include<limits.h>
#include<string.h>//strcat strcpy strlen  ; memset memcpy


int main()
{
	int n = 5;
	int m = 10;
	int* p = (int*)malloc(sizeof(int) * n);
	if (p == nullptr) exit(EXIT_FAILURE);
	for (int i = 0;i < n;i++)
	{
		p[i] = i + 10;
	}

	p = (int*)realloc(p, sizeof(int) * m);//在p指向空间的基础上增加到m个整型空间

	p = (int*)realloc(p, sizeof(int) * 2);//p指向的空间增加到2个整型空间(收缩)

	free(p);
	p = nullptr;

	return 0;
}

p指向的空间大小为5时:
在这里插入图片描述
p指向空间大小为10时:
在这里插入图片描述
p指向空间大小为2时:
在这里插入图片描述
realloc 函数调整内存空间大小有3种情况:

情况一:

ip指针不变,只需要将下越界标记向下移动,将头部信息修改
在这里插入图片描述

情况二:

ip指向新的空间
在内存空间足够大的地方重新开辟m个内存空间,将原本的数据复制进新空间,并将新空间的地址赋值给ip,并且把ip以前指向的旧空间释放
在这里插入图片描述

情况三:

在这里插入图片描述

内存不足,ip = (int*)realloc(p, sizeof(int) * m);,以这种情况写的代码会出现内存泄露。因为ip指针被赋值为空,但是ip指向的空间还没有被释放,当用户释放该空间时没有地址指向,所以内存泄露
在这里插入图片描述
代码修改避免内存泄露

int* newp = (int*)realloc(p, sizeof(int) * m);//在p指向空间的基础上增加到m个整型空间
	if (newp == nullptr)
	{
		printf("内存不足\n");
		exit(EXIT_FAILURE);
	}
	p = newp;

	p= (int*)realloc(p, sizeof(int) * 2);//p指向的空间增加到2个整型空间(收缩)
	//收缩空间一定会成功不用在意是否失败返回nullptr

	free(p);
	p = nullptr;

扩充空间ip为nullptr怎么办?

realloc扩充空间时如果ip为空时,realloc会退化为malloc ,有可能申请空间成功,也有可能申请空间失败
在这里插入图片描述

使用realloc()收缩空间

收缩空间一定都是成功的,都是讲下越界标记向上提升到指定位置。缺点就是形成内存碎片

int main()
{
	int* ip = nullptr;
	int n = 10;
	int* newdata = (int*)realloc(ip, sizeof(int) * n);
	//等价于:int *newdata=(int*)malloc(sizeof(int)*n);
	if (newdata == nullptr)
	{
		exit(EXIT_FAILURE);
	}
	ip = newdata;
	for (int i = 0;i < n;i++)
	{
		ip[i] = i + 10;
	}

	ip = (int*)realloc(ip, sizeof(int) * 2);

	free(ip);
	ip = nullptr;
	return 0;

}

收缩前:
在这里插入图片描述
收缩后:
在这里插入图片描述

真实的内存碎片

在这里插入图片描述

free

free(p):释放时并不是将p释放,而是将p指向的空间从已用状态,变成未使用状态,且只能释放一次,释放之后一定将p=nullptr,防止失效指针

申请成功多少空间,系统必须释放相应大小的空间,不能多,也不能少。

void free(void* ptr)
{
	if (nullptr == ptr)return;


}

在这里插入图片描述

注意一:

指针不赋nullptr,二次释放

在这里插入图片描述

在这里插入图片描述
所以free(ip);
一定要:ip=nullptr;

注意二:

指针不赋nullptr,再次申请到同一块内存空间,死都不知道怎么死的

在这里插入图片描述

注意三:

在这里插入图片描述

malloc时申请了多少空间可以计算出来;但是释放的是指针指向的连续空间,你怎么知道需要释放多少字节
解释一:

  • 当malloc时,堆区申请到空间后,指针指向改空间,此外改空间上面有空间大小4字节的上越界标志,改空间下面有空间大小4字节的下越界标志,可以将改指针强转换成char类型,一直往下走,看是否到达下越界标志,这样就可以计算出申请的空间大小
  • 本质:每次malloc申请空间成功时,初4字节的上/下越界标志,还要28字节(windows系统下)的头部信息,在这28字节中专门有4字节存放申请成功的空间字节个数,释放改空间是,指针向上迁移32字节(4+28)就可以读出头部信息,从而知道当时申请的空间大小,就意味着释放多少字节大小

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

内存泄露

错误理解:光malloc 没有free
真正意义上的内存泄漏:

在进行空间申请的时候一定要妥善保管malloc返回的地址;若malloc申请的空间用ip指向,不能轻易改变或者丢失ip的地址。一旦ip不指向该空间就会造成内存泄露。用户层面来说,一旦ip不能指向原本malloc的空间,就无法将申请的空间释放掉。内存泄漏的本质是丢失了malloc传回的指针

  • 拿malloc申请空间,丢失了malloc申请成功返回的地址(一旦丢失该地址,从某种意义上讲当前程序无法做到将该地址释放掉)。这种情况类似于没有释放该指针,又将该指针指向了其他空间。只要发生地址丢失就会产生内存泄漏

在这里插入图片描述
此时free只是释放了指向400个字节空间大小的ip,而指向40个字节空间大小的ip释放不了

  • 当程序执行的过程中,光malloc,没有free最终会将堆区3G的内存空间申请完了。

(三)易错点:

归纳:

  • 1 .动态开辟内存一定要判空
  • 2 .空间分配成功返回的地址,不要轻易改变(不要++或者–),因为一旦改变,起始地址就改变了,释放时指针会向上偏移4+28个字节读取头部信息,一旦起始地址改变,释放的空间就全乱套了
  • 3 .申请成功多少空间,系统必须释放相应大小的空间,不能多,也不能少。
  • 4 .内存泄露
  • 5 .malloc(0)并不意味着返回nullptr
  • 6.不正确的使用申请到的空间(例如原本申请到5个空间,但是赋值时赋了10个值,导致下越界标记被修改)程序虽然可以正常运行,可以正常打印该空间的值,但是会导致程序结束,释放该空间时程序崩溃。

在这里插入图片描述

(四)动态开辟二维数组

二级指针法:

在这里插入图片描述
动态开辟二维数组与静态二维数组的区别:
在这里插入图片描述

int** Get2Arrary(int row, int col)
{
	int** s = (int**)malloc(sizeof(int*) * row);
	if (s == nullptr) exit(1);
	for (int i = 0;i < row;++i)
	{
		s[i] = (int*)malloc(sizeof(int) * row);
		if (s[i] == nullptr) exit(1);
	}
	return s;
}

void Init_2Ar(int** s, int row, int col)
{
	assert(s != nullptr);
	for (int i = 0;i < row;++i)
	{
		for (int j = 0;j < col;++j)
		{
			s[i][j] = i + j;
			printf("%4d", s[i][j]);
		}
	}
}

void Free_2Ar(int** s, int row)
{
	assert(s != nullptr);
	for (int i = 0;i < row;++i)
	{
		free(s[i]);
	}
	free(s);
}

int main()
{
	int** s = nullptr;
	int row, col;
	scanf_s("%d %d", &row, &col);
	s = Get2Arrary(row, col);
	Init_2Ar(s, row, col);

	Free_2Ar(s, row);

	s == nullptr;
	return 0;
}

结构体法:

typedef int ElemType;
struct Array2
{
	ElemType* data;
	int row;
	int col;
};


void Init_Ar(Array2* br, int row, int col)
{
	assert(br != nullptr);
	br->row = row;
	br->col = col;
	br->data = (ElemType*)malloc((sizeof(ElemType)) * (br->row) * (br->col));
	if (br->data == nullptr) exit(1);
	int len = (br->row) * (br->col);
	for (int i = 0;i < len;++i)
	{
		br->data[i] = i + 1;
	}
}


ElemType GetItem(const Array2& par, int r, int c)
{
	//assert(par != nullptr);引用不用判空
	assert(r < par.row&& c < par.col);

	return par.data[(r * (par.col)) + c];
}

void SetItem(const Array2& par, int r, int c, ElemType val)
{
	assert(r < par.row&& c < par.col);
	par.data[(r * (par.col)) + c] = val;
}

void Destory(struct Array2& par)
{
	free(par.data);
	par.data = nullptr;
	par.col = par.row = 0;
}
int main()
{
	Array2 ar;
	int row, col;
	scanf_s("%d %d", &row, &col);
	Init_Ar(&ar, row, col);

	for (int i = 0;i < row;++i)
	{
		for (int j = 0;j < col;++j)
		{
			SetItem(ar, i, j, i + j);
		}
	}

	for (int i = 0;i < row;++i)
	{
		for (int j = 0;j < col;++j)
		{
			printf("%4d", GetItem(ar, i, j));
		}
		printf("\n");
	}
	printf("\n");  

	Destory(ar);//不调用销毁函数,内存也不会泄露,当程序整体结束,malloc的空间系统会自动释放

	return 0;
}

代码升级:返回值为引用

typedef int ElemType;
struct Array2
{
	ElemType* data;
	int row;
	int col;
};


void Init_Ar(Array2* br, int row, int col)
{
	assert(br != nullptr);
	br->row = row;
	br->col = col;
	br->data = (ElemType*)malloc((sizeof(ElemType)) * (br->row) * (br->col));
	if (br->data == nullptr) exit(1);
	int len = (br->row) * (br->col);
	for (int i = 0;i < len;++i)
	{
		br->data[i] = i + 1;
	}
}


ElemType& Item(struct Array2& par, int r, int c)
{
	assert(r < par.row&& c < par.col);
	return par.data[(r * (par.col)) + c];
}

int main()
{
	Array2 ar;
	int row, col;
	scanf_s("%d %d", &row, &col);
	Init_Ar(&ar, row, col);

	for (int i = 0;i < row;++i)
	{
		for (int j = 0;j < col;++j)
		{
			Item(ar, i, j)= i + j;
		}
	}

	for (int i = 0;i < row;++i)
	{
		for (int j = 0;j < col;++j)
		{
			printf("%4d", Item(ar, i, j));
		}
		printf("\n");
	}
	printf("\n");

	

	return 0;
}

(五)拓展:动态开辟N维数组

三维数组:

typedef int ElemType;
struct Array3
{
	ElemType* data;
	int row;
	int col;
	int high;
};

在这里插入图片描述

N维数组:

typedef int ElemType;
struct Array_N
{
	ElemType* data;
	int* index;//下标
	int n;//维度
};

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值