常见的动态内存错误
3. 常见的动态内存错误
在进行动态内存管理时,有很多需要注意的,一旦我们使用不当,就有可能会导致错误的发生。
接下来我们就来总结一下,哪些操作可能会引发动态内存错误。
3.1 对NULL指针的解引用操作
通过上面的学习我们已经知道了,malloc,realloc,calloc在开辟空间时,一旦开辟失败,就会返回空指针,如果我们不小心对这些空指针进行了解引用,就会导致错误的发生。
举个例子:
int main()
{
int* p = (int*)malloc(4);
*p = 20;//如果p的值是NULL,就会有问题
free(p);
p = NULL;
return 0;
}
大家看这段代码有问题吗?
因为malloc有可能返回空指针,所以像上面这样不做判断,直接对malloc返回的指针,解引用,就可能会导致问题出现。
我们写出这样的代码,有的编译器可能就直接会报警告:
不过上面的代码中我们申请的空间比较小,只有4个字节,可能不会申请失败。
如果我们要申请一块特别大的空间,很有可能就会开辟失败:
我们来试一下:
int main()
{
int* p = (int*)malloc(INT_MAX);
*p = 20;//如果p的值是NULL,就会有问题
free(p);
p = NULL;
return 0;
}
开辟INT_MAX字节的空间,INT_MAX是整型的最大值:
这次,很有可能就会失败,所以我们最后加一个判断:
int main()
{
int* p = (int*)malloc(INT_MAX);
if (p == NULL)
{
perror("malloc");
return 1;
}
else
{
*p = 20;
}
free(p);
p = NULL;
return 0;
}
这里的perror也是一个打印错误信息的函数(和strerror差不多),不过它可以在前面加上我们自定义的信息。
我们看结果是什么:
说明,这里malloc就开辟失败了,返回的是空指针。
所以,对于
malloc,realloc,calloc
的返回值,我们一定要进行一个检查,防止对空指针解引用。
3.2 对动态开辟空间的越界访问
对动态开辟空间的越界访问,也会发生错误。
举个例子:
int main()
{
int i = 0;
int* p = (int*)malloc(5 * sizeof(int));
assert(p);//断言,防止p为空指针
for (i = 0; i <= 10; i++)
{
*(p + i) = i;//越界访问
printf("%d ", *p);
}
free(p);
p = NULL;
return 0;
}
上面的代码中,我们使用malloc开辟了5个整型大小的空间,即20个字节,那p 作为1个整型指针,加1跳过4个字节,那我们循环10次,是不是就越界访问了啊。
这时我们运行就出错了:
因为我们越界访问了。
所以,也注意不能越界访问。
3.3 对非动态开辟内存使用free释放
这个我们在上面其实也已经提到过了。
我们要知道,free是用来释放动态开辟的内存空间的,
如果我们用free去释放非动态开辟的内存,此时free的行为是标准未定义的。
比如:
int main()
{
int num = 10;
int* p = #
free(p);
p = NULL;
return 0;
}
num 是我们定义的局部变量,是保存在栈区的,而堆区才是用来动态内存分配的。
这样的代码运行,可能是会出错的。
所以我们不要用free去释放非动态开辟的内存。
3.4 使用free释放一块动态开辟内存的一部分
什么意思呢?
我们在使用free释放一块动态开辟的内存空间的时候,传给free那个指针必须是指向这块空间的起始位置。
如果在使用过程中,原本指向内存块起始位置的指针发生了改变,不再指向该空间的起始位置,那我们最后用free去释放这块空间的时候,就不能再传这个指针了。
举个例子:
int main()
{
int* p = (int*)malloc(10);
assert(p);
p++;
free(p);//p不再指向动态内存的起始位置
p = NULL;
return 0;
}
这样的操作就是错误的,我们free(p)
的时候,p 已经不再指向这块动态内存的起始位置了。
运行这样的代码,程序就出错了。
如果确实需要改变:
我们可以再创建一个指针变量,保存一下最初指向起始地址的指针,这样最后释放的时候,我们依然能找到起始位置的地址。
像这样:
int main()
{
int* p = (int*)malloc(10);
assert(p);
int* ptr = p;//用ptr保存malloc开辟空间的起始位置
p++;
free(ptr);//释放的时候传ptr
ptr = NULL;
p = NULL;
return 0;
}
这样,程序就不会出错了。
3.5 对同一块动态内存多次释放
什么意思呢?
我们动态开辟一块内存空间,使用完直接释放了,并且没有将指向该内存块起始位置的指针(假设是指针p)置空,过了一会儿可能忘记已经释放过了,然后再后面又把p传给free,又对这块空间进行一次释放。
这样的话我们运行代码就也会出错的。
像这样:
int main()
{
int* p = (int*)malloc(100);
free(p);
///.....;
free(p);//重复释放
return 0;
}
这样程序是会崩掉的。
为了避免这种情况发生:
我们在释放掉p指向的空间之后,要及时将p置空。
int main()
{
int* p = (int*)malloc(100);
free(p);
p = NULL;
///.....;
free(p);//重复释放
return 0;
}
这样,虽然我们释放了两次,但因为我们第二次传的是空指针,所以不会有问题。
因为如果free的参数 ptr 接收的是NULL指针,不执行任何操作。
所以:
在使用free释放一块动态内存空间后,及时将指向起始位置的指针置空是一个好习惯。
3.6 动态开辟内存忘记释放(内存泄漏)
就是我们动态开辟的空间在使用完之后,一定要记得释放,不释放的话有可能会造成内存泄漏。
切记:
动态开辟的空间一定要释放,并且正确释放 。
- 点赞
- 收藏
- 关注作者
评论(0)