C语言内存管理

一、栈、堆和静态区:

1、堆:由maloc系列函数或new操作符分配的内存。其生命周期由free或delete决定。在没有释放之前一直存在,直到程序结束。其特点是使用灵活,空间比较大,但容易出错。

2、栈:保存局部变量。栈上的内容只在函数的范围内存在,当函数运行结束,这些内容也会自动被销毁。其特点是效率高,但空间大小有限。

3、静态区:保存自动全局变量和static变量(包括static全局和局部变量)。静态区的内容在整个程序的生命周期内都存在,由编译器在编译的时候分配。

 二、常见的内存错误及对策

1、指针没有指向一块合法的内存

定义了指针变量,但是没有为指针分配内存,即指针没有指向一块合法的内存。

1.1 结构体成员指针未初始化

#include <stdio.h>
#include <string.h>

//结构体成员指针未初始化
typedef struct STU {
	char *name;
	float score;
};

int main() {

	STU stu1, *pstu;
	strcpy(stu1.name, "Hello");
	stu1.score = 100;
	printf("%s\t%f\n",stu1.name,stu1.score);
	return 0;
}

 运行代码:

能编译,但是不能运行:

正确方式:

剖析原因:定义的结构体变量stu1,分配了char *类型的指针(指针变量name本身只分配了4个字节)和int类型的变量score;而nam指针并没有指向一个合法的地址。

正确的做法是:为name指针变量malloc一块空间。

stu1.name = (char *)malloc(10);

注意:分配的内存单元一定要大于等于(字符串长度+‘\0’)

正确代码:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
//结构体成员指针未初始化
typedef struct STU {
	char *name;
	float score;
};

int main() {

	STU stu1, *pstu;
	stu1.name = (char *)malloc(10);
	strcpy(stu1.name, "Hello");
	stu1.score = 100;
	printf("%s\t%f\n",stu1.name,stu1.score);
	return 0;
}

运行结果:

1.2 没有为结构体指针分配足够的内存

没有为结构体指针分配足够的内存:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
//结构体成员指针未初始化
typedef struct STU {
	char *name;
	float score;
};

int main() {

	STU stu1, *pstu;
	pstu = (STU *)malloc(sizeof(STU *));
	strcpy(pstu->name,"World");
	pstu->score = 98;
	printf("%s\t%f\n", pstu->name, pstu->score);
	return 0;
}

 程序能编译,运行结果错误:

原因:为pstu分配内存的时候,分配的内存大小不合适。这里把sizeof(STU),误写为sizeof(STU *)。当然name指针同样没有被分配内存,解决办法同上!

pstu = (STU *)malloc(sizeof(STU ));
pstu->name = (char *)malloc(10);

正确程序:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
//结构体成员指针未初始化
typedef struct STU {
	char *name;
	float score;
};

int main() {

	STU stu1, *pstu;
	pstu = (STU *)malloc(sizeof(STU ));
	pstu->name = (char *)malloc(10);
	strcpy(pstu->name,"World");
	pstu->score = 98;
	printf("%s\t%f\n", pstu->name, pstu->score);
	return 0;
}

运行结果:

2、内存泄漏

会产生泄露的内存就是堆上的内存(这里不讨论资源、句柄等泄露情况),也就是说由malloc系列函数或new操作符分配的内存。如果用完之后没有及时free或delete,这块内存就无法释放,直到整个程序终止。

2.1 如何使用malloc函数

函数原型: void * malloc(int size )

malloc函数的返回值是一个void类型的指针,参数为int类型数据,即申请分配的内存大小,单位是字节。内存分配成功之后,malloc函数返回这块内存的首地址,你需要一个指针来接收这个地址。但是由于函数的返回值是void * 类型的,所i必须强制转换成你所接收的类型。也就是说,这块内存将要用来存储什么类型的数据,比如:

char *p=(char *)malloc(100);

在堆上分配了100字节的内存,返回这块内存的首地址,把地址强制转换成char * 类型后赋给char *类型的指针变量p;同时告诉我们这块内存将用来存储char 类型的数据。也就是说你只能通过指针变量p来操作这块内存。这块内存本身并没有名字,对它的访问是匿名访问。

注意:malloc函数申请的是连续的一块内存。

2.2 内存释放

既然有分配,那就必须有释放。不然的话,有限的内存总会用光,而没有释放的内存却在空闲。与malloc对应的就是free函数了。free函数只有一个参数,就是所要释放的内存快的首地址,接上例则为:

free(p);

free函数看上去挺狠的,但它到底做了什么呢?

其实它就做了一件事:斩断指针变量与这块内存的关系。比如上面的例子,我们可以说 malloc 函数分配的内存块是属于p的,因为我们对这块内存的访问都需要通过 p 来进行。free 函数就是把这块内存和p之间的所有关系斩断,从此 p 和那块内存之间再无瓜葛。至于指针变量 p 本身保存的地址并没有改变,但是它对这个地址处的那块内存却已经没有所有权了。那块被释放的内存里面保存的值也没有改变,只是再也没有办法使用了。

在程序中,malloc和free一定要成对出现,出现次数相等。malloc两次,free一次,会出现内存泄漏;malloc一次,free两次肯定会出错。遵循:一夫一妻制!

2.3 内存释放之后

既然使用 free 函数之后指针变量 p 本身保存的地址并没有改变,那我们就需要重新把 p 的值变为NULL;

p=NULL;

这个 NULL 就是我们前面所说的“栓野狗的链子”,如果你不栓起来迟早会出问题的。比如:

char *p = (char *)malloc(100);
strcpy(p,"hello");
free(p); //p所指的内存被释放,但是p所指的地址仍然不变
…
if (p != NULL) {
	
	//没有起到防错作用
	strcpy(p,"world");
}

释放完块内存之后,没有把指针置NULL,这个指针就成为了“野指针”。这是很危险的,而且也是经常出错的地方。所以一定要记住一条:free之后,一定要给指针置NULL。

2.4 内存已经被释放了,但是继续通过指针来使用

这里一般有三种情况:

1、就是上面所说的,free(p)之后,继续通过p指针来访问内存。解决的办法就是给 p 置NULL

2、函数返回栈内存,这是初学者最容易犯的错误。比如在函数内部定义了一个数组,却用 return 语句返回指向该数组的指针。解决的办法就是弄明白栈上变量的生命周期。

3、内存使用太复杂,弄不清到底哪块内存被释放,哪块没有被释放。解决的办法就是重新设计程序,改善对象之间的调用关系。

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值