一、栈、堆和静态区:
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、内存使用太复杂,弄不清到底哪块内存被释放,哪块没有被释放。解决的办法就是重新设计程序,改善对象之间的调用关系。