第二部分主要是常见的动态内存错误
动态内存错误
1.对NULL指针的解引用操作
对NULL指针的解引用操作,什么意思呢?有些同学写代码的时候比较冲动,如下:
int main(){
int *p= (int *)malloc(40);
for (int i = 0; i < 10; ++i) {
*(p+1)=i;
}
free(p);
p=NULL;
return 0;
}
一般我们开辟空间并进行赋值的时候会去这样写,看似申请了空间也初始化了也释放了,没有问题。但是,如果开辟空间失败了呢?如果没有申请到这4个字节的空间呢?那么此时p就被赋值为NULL了
所以for循环中p为NULL 不管加1还是加2 或者加n都是非法地址,对非法地址解引用是不是就出现问题了?
所以这里一定要对p进行判断。
int main(){
int *p= (int *)malloc(40);
if (p!=NULL){//在这里加上对p的是否为NULL判断。
for (int i = 0; i < 10; ++i) {
*(p+1)=i;
}
}else{
printf("%s\n", strerror(errno));
}
free(p);
p=NULL;
return 0;
}
2.对动态开辟空间的越界访问
我们看下面这个例子
int main(){
int *p=(int *) malloc(5*sizeof (int));
if (p==NULL){
return 0;
} else{
for (int i = 0; i < 10; ++i) {
*(p+i)=i;
}
}
free(p);
p=NULL;
return 0;
}
输出结果:
大眼一瞅好像没什么问题,也判断p是否为NULL了也释放空间了。并且赋值了10个输出了10个数好像也没有报错。我这里没有报错是因为我的编译器以及我开辟的这片空间后面刚好可以满足继续越界访问。但是这是非法操作。换个机器换个编译器可能程序就要崩溃或者报错的。
但是 我们要注意:在申请空间的时候只申请了5个int类型变量大小的空间,也就是20个字节,但是你在使用的时候循环赋值,赋值了10次,也就是对10个int类型变量进行赋值,那空间明明没有那么大,你却要这样操作,这就出现了越界访问。
3.对非动态开辟内存使用free释放
举个例子:
有的同学写代码写迷糊了,在栈区开辟的空间,最后也适用free给释放掉了。
int main(){
int a=10;
int *p=&a;
*p=20;
free(p);
p=NULL;
return 0;
}
输出结果为
注意 a这个空间是在栈区申请的一段空间,而free函数是对堆区空间的释放。如果这样执行,程序就会报错。
4.使用free释放动态开辟内存的一部分
我们先看下面这个代码
int main(){
int *p= (int *)malloc(40);
if (p==NULL){
return 0;
}
for (int i = 0; i < 10; ++i) {
*p++=i;
}
//回收空间
free(p);
p=NULL;
}
看似逻辑是申请了40个字节的空间,然后对这是个int类型进行初始化,然后也判断是否申请成功,也执行空间回收了,但是我们看一下运行结果。
报了一个错误:
那么这里是为什么会出现这种情况呢?
我简单画了个示意图,我们看到
*p++=i;
这行代码起始改变了p这个指针的位置,如果我们初始化完毕之后,p已经指向最后一个数字的后面的地址了,如果此时在进行释放p的空间的话,已经不是我们原本申请的40个字节了。只要不是指向最初的位置,无论p指向哪都只是空间的一部分,所以不能使用free。
所以我们只能使用:
*(p+i)=i
这种方式可以正常赋值。因为它并没有改变p的指针指的位置。
5.对同一块动态内存的多次释放
我们看下面这段代码:
int main(){
int *p=(int*) malloc(40);
if (p==NULL){
return 0;
}
free(p);
free(p);
p=NULL;
return 0;
}
申请了一段内存空间,然后对这段空间释放了两次。就会报错:
显示第二次释放的空间还没有被分配。所以对空间的回收要遵守:谁开辟谁回收的原则。
另外在释放之后将p赋值为NULL也可以避免这种情况。
6.忘记释放动态开辟的空间(内存泄漏)
int main(){
while (1){
malloc(1);
// Sleep(1000);
}
return 0;
}
执行这段代码,我们可以从任务管理器中直观的看到你的内存正在不断被占用,内存使用会变的越来越大。这个会出现死机的风险的。就是因为只开辟空间而不回收你开辟的空间造成的。
切记:动态开辟的空间一定要释放,并且正确释放。
这就是内存泄漏。
总结
这就是C语言动态内存管理的第二部分的内容,主要是总结一些,代码上常见的错误,这六种错误需要小心。
下一个部分的内容准备出一些经典的面试题有关C语言动态内存管理相关的。