C语言中6个常见的动态内存的错误详解(能看懂文字就能明白系列)

系列文章目录

个人主页:古德猫宁-

信念如阳光,照亮前行的每一步


文章目录

  • 系列文章目录
    • *信念如阳光,照亮前行的每一步*
  • 前言
  • 一、对NULL指针的解引用操作
  • 二、对动态开辟空间的越界访问
  • 三、对非动态开辟内存使用free释放
  • 四、使用free释放一块动态开辟内存的一部分
  • 五、 对同一块动态内存多次释放
  • 六、动态开辟内存忘记释放(内存泄漏)
  • 总结


前言

本节目标:前面的文章介绍了C语言的动态内存开辟,本文重点讲述常见的动态内存的错误


一、对NULL指针的解引用操作

void test()
{
	int* p = (int*)malloc(sizeof(int));
	*p = 20;
	free(p);
}

解释:

这段代码的主要目的是使用动态内存分配(malloc)为一个整数分配内存,将其值设置为 20,然后使用 free 函数释放该内存。从代码的角度来看,没有显著的问题,但是有一些潜在的注意事项:

在实际的程序中,最好在调用 malloc 后检查分配是否成功。如果内存分配失败,malloc 将返回 NULL,释放内存后,我们最好将指针置为 NULL,以避免悬挂指针问题

修改后:

void test()
{
	int* p = (int*)malloc(10*sizeof(int));
	*p = 20;//如果p的值是NULL,就会有问题
	if (p == NULL)//判断分配是否成功
	{
		perror("malloc");//报错
		return 1;
	}
	free(p);//释放空间
	p = NULL;
}

二、对动态开辟空间的越界访问

int main()
{
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		return 1;
	}
	//使用
	int i = 0;
	for (i = 0; i <= 10; i++)
	{
		*(p + i) = i;//当循环到第11次时就越界访问了
	}
	//
	free(p);
	p = NULL;

	return 0;
}

C语言中6个常见的动态内存的错误详解(能看懂文字就能明白系列)_第1张图片
解释:

上面这段代码中使用 malloc 分配了 40 字节的内存,然后在循环中尝试写入 11 个整数。由于数组索引是从 0 开始的,因此当 i 等于 10 时,实际上是在访问 *(p + 10),这越过了为数组分配的内存范围。

所以我们可以这样改:

#include
int main()
 {
	int* p = (int*)malloc(11*sizeof(int));//分配足够的空间来存储11个整数
	if (p == NULL)
	{
		return 1;
	}
	//使用
	int i = 0;
	for (i = 0; i <=11; i++)
	{
		*(p + i) = i;//循环的上限改为 10,不会越界访问
	}
	//
	free(p);
	p = NULL;

	return 0;
}

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

int main()
{
	int a = 10;
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		return 1;
	}
	//使用
	//...
	p = &a;//p指向的空间就不再是堆区上的空间
	free(p);
	p = NULL;
	//....

	return 0;
}

C语言中6个常见的动态内存的错误详解(能看懂文字就能明白系列)_第2张图片
解释:

  • 这段代码存在一个问题,即在将指针 p 指向堆分配的内存后,又将其指向了一个栈上的变量 a,然后尝试使用 free(p)释放内存。这是不正确的,因为 p 指向的不再是通过 malloc 分配的堆内存,而是指向了栈上的变量。
  • 当我们使用 free 函数释放内存时,应该确保指针指向的是通过 malloc 或类似函数动态分配的内存块。在这种情况下,并没有释放通过 malloc 分配的内存,而是尝试释放栈上的变量,这可能导致未定义的行为。
  • 所以在释放通过 malloc 分配的内存,我们确保在释放之前不要改变指针 p 指向的位置。

修改后:

#include 

int main() {
    int a = 10;
    int* p = (int*)malloc(40);
    if (p == NULL) {
        return 1;
    }

    // 使用
    //...

    // 不要改变指针 p 指向的位置,不要执行 p = &a;
    
    // 释放内存
    free(p);
    p = NULL;

    //....

    return 0;
}

四、使用free释放一块动态开辟内存的一部分

#include
int main()
{
	int a = 10;
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		return 1;
	}	
	//使用
	p++;

	//释放
	free(p);
	p = NULL;

	return 0;
}

C语言中6个常见的动态内存的错误详解(能看懂文字就能明白系列)_第3张图片

解释:

  • 这段代码存在一个问题。在使用 malloc 分配内存后,我们将指针 p 递增了一个位置 (p++),然后尝试使用 free 函数释放内存。这样做是不正确的,因为 p 不再指向 malloc 分配的起始位置,而是指向了分配内存块的下一个位置。free 函数要求传入的指针必须指向通过 malloc 等分配函数返回的精确地址。
  • 如果对指针进行了移动,应该确保在释放内存之前将指针重新指向 malloc 分配的起始位置。

修改后:

#include 

int main() {
    int a = 10;
    int* p = (int*)malloc(40);
    if (p == NULL) {
        return 1;
    }

    // 使用
    p++; // 移动指针

    // 重新指向起始位置
    p--;

    // 释放内存
    free(p);
    p = NULL;

    return 0;
}

五、 对同一块动态内存多次释放

int main()
{
	int a = 10;
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		return 1;
	}
	//使用

	//释放
	free(p);
	p = NULL;

	free(p);
	p = NULL;

	return 0;
}

C语言中6个常见的动态内存的错误详解(能看懂文字就能明白系列)_第4张图片
解释:

  • 上面这段代码存在一个问题,即在释放内存后又尝试再次释放相同的内存块。这是不正确的,因为一旦通过 free 函数释放了内存,就不应该再次使用相同的指针来释放。这可能导致未定义的行为,甚至程序崩溃。
  • 代码中,第一次调用 free(p) 将内存释放了,并将指针 p 设置为 NULL。然后,你又尝试使用 free(p),这是不安全的。

修正这个问题的方法是只调用一次 free,而不是两次:

#include 

int main() {
    int a = 10;
    int* p = (int*)malloc(40);
    if (p == NULL) {
        return 1;
    }
    // 使用

    // 释放内存
    free(p);
    p = NULL;

    // 避免再次释放相同的内存块
    // free(p); // 这一行应该注释掉或删除

    return 0;
}

六、动态开辟内存忘记释放(内存泄漏)

void test()
{
    int* p = (int*)malloc(100);
    if (NULL != p)
    {
        *p = 20;
    }
    free(p);
    p = NULL;
}

int main()
{
    test();
    //
    while (1);
}

C语言中6个常见的动态内存的错误详解(能看懂文字就能明白系列)_第5张图片
解释:

  • 上面这段代码中,在结尾没有调用 free 来释放动态内存分配的空间,而且main函数无限循环,这个函数被调用多次,每次都分配内存但没有释放,程序将持续占用更多的内存,最终可能导致系统资源(内存池)不足,这类问题称为内存泄漏。

修改后:

void test()
{
    int* p = (int*)malloc(100);
    if (NULL != p)
    {
        *p = 20;
    }
    free(p);
    p = NULL;
}

int main()
{
    test();
    return 0; // 添加这行以正常退出程序
}

忘记释放不再使用的动态开辟的空间会造成内存泄漏
切记:动态开辟的空间一定要释放,并且正确释放。


总结

各位要对上面常见的动态内存开辟有一个清晰的认知,并避免这些问题的出现,今天的笔记到此结束啦

你可能感兴趣的:(c语言,开发语言,学习,笔记,经验分享)