常见的动态内存错误

举报
YIN_尹 发表于 2023/08/07 20:10:18 2023/08/07
【摘要】 3. 常见的动态内存错误在进行动态内存管理时,有很多需要注意的,一旦我们使用不当,就有可能会导致错误的发生。接下来我们就来总结一下,哪些操作可能会引发动态内存错误。3.1 对NULL指针的解引用操作通过上面的学习我们已经知道了,malloc,realloc,calloc在开辟空间时,一旦开辟失败,就会返回空指针,如果我们不小心对这些空指针进行了解引用,就会导致错误的发生。举个例子:int m...

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返回的指针,解引用,就可能会导致问题出现。

我们写出这样的代码,有的编译器可能就直接会报警告:

de83394ab48a49abbaa2743a019b8f99.png

不过上面的代码中我们申请的空间比较小,只有4个字节,可能不会申请失败。

如果我们要申请一块特别大的空间,很有可能就会开辟失败:

我们来试一下:

int main()
{
    int* p = (int*)malloc(INT_MAX);
    *p = 20;//如果p的值是NULL,就会有问题
    free(p);
    p = NULL;
    return 0;
}

开辟INT_MAX字节的空间,INT_MAX是整型的最大值:

93c4ccd19fce4c97acae11ea79ebd8be.png

这次,很有可能就会失败,所以我们最后加一个判断

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差不多),不过它可以在前面加上我们自定义的信息。

我们看结果是什么:

cf41cbfe49764a81a563178353c05f0f.png

说明,这里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次,是不是就越界访问了啊。

这时我们运行就出错了:

0fd2c4b827a94b3b98ef1d89e907b76e.png

因为我们越界访问了。

所以,也注意不能越界访问。


3.3 对非动态开辟内存使用free释放

这个我们在上面其实也已经提到过了。


我们要知道,free是用来释放动态开辟的内存空间的,

如果我们用free去释放非动态开辟的内存,此时free的行为是标准未定义的。


比如:

int main()
{
    int num = 10;
    int* p = &num;
    free(p);
    p = NULL;
    return 0;
}

num 是我们定义的局部变量,是保存在栈区的,而堆区才是用来动态内存分配的。

这样的代码运行,可能是会出错的。

e57e45eb72b84962b337e7be3979bd3c.png

所以我们不要用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 已经不再指向这块动态内存的起始位置了。

运行这样的代码,程序就出错了。

656162a3e78048cda0a4eb3976ccad8c.png

如果确实需要改变:

我们可以再创建一个指针变量,保存一下最初指向起始地址的指针,这样最后释放的时候,我们依然能找到起始位置的地址。

像这样:

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;
}

55cc04aaa0c940cfb05523cc2cbb2667.png

这样程序是会崩掉的。

为了避免这种情况发生:

我们在释放掉p指向的空间之后,要及时将p置空

int main()
{
    int* p = (int*)malloc(100);
    free(p);
    p = NULL;
    ///.....;

    free(p);//重复释放
    return 0;
}

这样,虽然我们释放了两次,但因为我们第二次传的是空指针,所以不会有问题。

因为如果free的参数 ptr 接收的是NULL指针,不执行任何操作。

所以:

在使用free释放一块动态内存空间后,及时将指向起始位置的指针置空是一个好习惯。

3.6 动态开辟内存忘记释放(内存泄漏

就是我们动态开辟的空间在使用完之后,一定要记得释放,不释放的话有可能会造成内存泄漏。

切记:

动态开辟的空间一定要释放,并且正确释放

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。