C语言malloc函数解释和calloc函数,free()

malloc函数

内存分为堆区(.heap),栈区(.stack),数据区(.data),代码区(.code)。 全局变量和静态变量存放在静态区,局部变量存在在栈区,动态申请的变量(即new,malloc函数申请的变量)存放在堆区。
根据之前的理解,全局变量和字符串常量存放于.data数据区中。而局部变量存放于栈区.stack
malloc用于开辟内存空间,free用于释放空间,如果申请了内存空间不用后,就一定要释放否则会出现内存泄漏,当不断的申请空间而不释放空间,最终会出现内存被“吃光”,当内存不足时返回NULL

堆空间的开辟 malloc(#include<malloc.h>)
其目的是为了达到有限空间动态使用(开辟收回),从而最大化(内存)资源利用率
但是有好处就有对应的坏处,动态开辟空间会增加时间的消耗,而且需要注意开辟了就需要free来释放

在不使用malloc动态开辟空间之前,我们一直使用的都是数组来存放数据,但是使用数组的特性是大开小用,要开的大从而防止不够,但是这样会有许多空间的浪费,尤其是在堆数据数目的不确定的情况下使用数组是十分不合理,或者说设计错误的地方,那么我们就需要malloc动态开辟空间来存储数据

void malloc( size_t size );
功能: 函数指向一个大小为size的空间,如果错误发生返回NULL。 存储空间的指针必须为堆,不能是栈。
这样以便以后用free函数释放空间。
注意malloc的返回值是void
类型的指针,所以在使用是我们需要对返回值进行强转

int main()
{
	int *ip=NULL;
	char *cp=NULL;
	int n=10;
	ip=(int*)malloc(sizeof(int)*n);//开辟40个字节
	cp=(char*)malloc(sizeof(char)*n);//开辟10个字节的内存空间
	for(int i=0;i<10;i++)
		ip[i]=i;
	free(ip);
	free(cp);
	return 0;
}

需要注意的时如果上面的程序改为

int main()
{
	int *ip=NULL;
	int n=10;
	ip=(int*)malloc(sizeof(int)*n);//开辟40个字节
	for(int i=0;i<10;i++)
		*ip++=i;
	free(ip);
	return 0;
}

上面的程序虽然能够编译通过,但是在程序执行阶段就会崩溃,因为可以看到
我们在对*ip赋值时,我们把ip的指向进行了移动,那么在free(ip)时,由于ip的位置出现了移动所以出现了错误,总之,malloc开辟了多少空间,在free时就必须释放同等大小,同等位置的空间,所以ip的指向坚决不能改变面对这种情况如果我们必须改变ip的位置时
对应的做法因该为:

int main()
{
	int *ip=NULL;
	int *ip2=ip;
	int n=10;
	ip=(int*)malloc(sizeof(int)*n);//开辟40个字节
	for(int i=0;i<10;i++)
		*ip2++=i;
	free(ip);
	return 0;
}

这样我们使用ip2代替了ip进行移动,即进行了移动修改,又保证了ip的位置不变,从而保证free的正确执行

free的执行,对于malloc 申请的空间,系统会把这块空间的头部信息中的某个标记修改,
如该空间未被申请时标记位0,而申请过后标记变为1,这样保证了这块空间无法被二次申请。而在free执行的原理就时把这个标记改回为0,同时对原来申请过的空间进去整体置数cd cd cd cd把这块内存空间覆盖(若不覆盖可能会导致数据泄露),这样根据标记的变化来判断申请回收空间的决定因素(需要注意的时这个标记并不在这块空间的头部信息中而是处于更高的内存空间位置)

但是上面的程序还有一个不好的地方在于,虽然我们free(ip),但是ip依旧指向原来开辟的那块空间,并没有把它的指向修改,这样的后果是十分严重的,如果这个空间被某个程序malloc申请后,ip被错误调用则会对后来开辟的空间
的数据修改,那么会影响程序的正确性。
所以我们需要把free(ip)后的ip=NULL;
free(ip);
ip=NULL;

可以做一个例子验证:
int main()
{
int ip=NUll;
ip=(int
)malloc(sizeof(int));
*ip=100;
cout<<*ip;
free(ip);
cout<<*ip;
*ip=100;
cout<<*ip;
}
结果:
100
-572662307
100
请按任意键继续. . .

int *p=(int*)malloc(10);
p=(int*)malloc(100);

上面的操作就会造成内存泄漏
(当然也不能对同一块空间free两遍,否则会产生程序崩溃。)
如果想free两便可以这么做

free(p);
p=NULL;
free(p);

上面的程序是正确的,因为free函数本身就带有检查NULL的能力,所以当第一次释放后,在对p赋值为NULL后再次进行free操作,在函数内部检测为NULL则return不作为。
申请空间是需要以字节个数申请,使用前需要预编译#include<malloc.h>

void main()
{
	int *p = NULL;
	int n;
	cin >> n;
	p = (int*)malloc(sizeof(int)*n);
}
void main()
{
	int *p = NULL;
	int n;
	cin >> n;
	p = (int*)malloc(sizeof(int)*n);
	//p = (int*)malloc(4*n);不要这样使用,这样函数的通用性不高
	for (int i = 0; i < n; i++)
		p[i] = i;  //千万不能写成*p=i;p++因为malloc开辟了空间后需要在释放空间,
	    //*(p+i)           //如果把指针的位置移动那么在释放时就会出现问题
	for (int i = 0; i < n; i++)
		cout << p[i] << " " << *(p+i)<<" ";
	cout << endl;
	free(p);//free后p指针变为空悬指针,原本他的空间比如有一个标记时已用,free后它的空间性质变为未用,但还是指向那块空间
	p = NULL;//所以在free后一定要给他赋值为NULL
}

在这里配图进行对上面程序的解释,这里涉及小端存放内存存放形式
**需要注意的是高位数在高地址,所以从int型变量来看,内存监视为4列时为
0x00F7A790 00 00 00 00 …
0x00F7A794 01 00 00 00 …
0x00F7A798 02 00 00 00 …
0x00F7A79C 03 00 00 00 …
0x00F7A7A0 04 00 00 00 …
0x00F7A7A4 05 00 00 00 …
0x00F7A7A8 06 00 00 00 …
0x00F7A7AC 07 00 00 00 …
0x00F7A7B0 08 00 00 00 …
0x00F7A7B4 09 00 00 00 …

单个字节地址来看情况为:

0x00D5A540 02 .
0x00D5A541 00 .
0x00D5A542 00 .
0x00D5A543 00 .
0x00D5A544 03 .
0x00D5A545 00 .
0x00D5A546 00 .
0x00D5A547 00 .

如果我把这个地址的顺序改为从小到大的40个字节的内存块,那么它的表示为
在这里插入图片描述

int *getarray(int n)//不再生存期问题中,函数空间在栈中,malloc在堆中,虽然函数死亡,但是堆空间开辟的内存空间依旧存在
{
	int *s = (int *)malloc(sizeof(int)*n);
	return s;     //所以s在main中依旧能够在主函数中调用
}

void main()
{
	int n;
	cin >> n;
	int *p = getarray(n);
	for (int i = 0; i < n; i++)
		p[i] = i;
	for (int i = 0; i < n; i++)
		cout << p[i] << endl;
	free(p);
	p = NULL;
}

我们在使用malloc时尽可能开大空间,应为在内存空间中存放的不只是数据本身,还存在一些对数据的解释性数据,叫做头部信息,如果malloc的开的空间小但是,开辟次数很多的话,就会产生很多的头部信息,从而占用很多内存
malloc只负责开辟相应大小的空间,至于开辟空间的类型由程序员负责,
如:int *p=(int *)malloc(sizeof(int)*size);
他开辟的类型由强转决定,

int sp=(int)malloc(sizeof(int)x);//存放每一行的首地址,做二维数组的每个一维数组的首地址使用
for (int i = 0; i < x; i++)
{
sp[i] = (int
)malloc(sizeof(int)*y);
}

动态开辟二维数组和静态开辟二维数组的区别
1.开辟的位置不同,一个在堆一个在栈
2.访问方式不同
比如静态ar[1][2]和动态s[1][2];
s是二级指针

  • *( *(s+1)+2),s+1移动了一个一级指针的大小,解引用后的一级指针加2后解引用
  • *( *(ar+1)+2),ar+1的移动是移动了整个一维数组,指针指向第二个一维数组后解引用,得到一维数组的指针,
    在加2,是对一维数组的指向首地址的指针移动两位后解引用。
    所以他们主要的却别在于指针类型不同,加一的能力不同
    3.静态空间连续,动态开辟的空间不连续

另外解释一下calloc函数,它的本身很简单,大概来讲就是调用了malloc开辟空间后,如果成功开辟就会在调用memset函数,对开辟的空间进行初始化
calloc
函数原型:void *calloc(size_t n, size_t size);
功 能: 在内存的动态存储区中分配n个长度为size的连续空间,函数返回一个指向分配起始地址的指针;如果分配不成功,返回NULL。

再接着有一个重要的例题:需要动态开辟二维数组
这个题的关键是,我们要使用二级指针来开辟一个连续空间作为存储二维数组的地址的一维数组

void main()
{
	int row, col;
	cin >> row >> col;
	int **s;
	s = (int **)malloc(sizeof(int)*row);
	for (int i = 0; i < row; i++)
	{
		s[i] = (int *)malloc(sizeof(int)*col);
	}
	for (int i = 0; i < row; i++)
	{
		for (int j = 0; j < col;j++)
		{
			s[i][j] = i + j;
		}
	}
	for (int i = 0; i < row; i++)
	{
		for (int j = 0; j < col; j++)
		{
			cout << s[i][j];
		}
	}
}

  • 9
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值