动态内存错误代码分析及改正+面试题

错误代码分析

  1. 动态内存常见错误如下:
  2. 对NULL指针的解引用操作
  3. 对动态开辟空间的越界访问
  4. 对非动态开辟内存使用free释放
  5. 使用free释放一块动态开辟内存的一部分
  6. 对同一块动态内存多次释放
  7. 动态开辟内存忘记释放(内存泄漏)

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

错误原因分析

int main()
{
	//1
	int* p1 = malloc(INT_MAX);//此处申请INT_MAX这么大的空间,一定会返回失败,而malloc函数申请失败的返回值是NULL,所以p1得到的值是NULL
	*p1 = 1;//此处对空指针解引用,就会报错
    free(p1);
    p1 = NULL;
    return 0;
}

改正

int main()
{
	//1
	int* p1 = malloc(INT_MAX);
    if(p1 == NULL)//增加对空指针的判断
    {
        return;
    }
    else
    {
        *p1 = 1;
    }
	free(p1);
    p1 = NULL;
    return 0;
}

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

错误原因分析

int main()
{
    //2
	int* p2 = malloc(20);//相当于只申请了5*sizeof(int)大小的空间
    if(p2 == NULL)//如果p2是空指针,就直接返回
    {
        return;
    }
	for (int i = 0; i < 10; i++)//这个for循环访问了10个int的空间,越界访问了
	{
		*(p2 + i) = i;//此处可以修改
	}
    free(p2);//但是会free失败
    p2 = NULL;
    return 0;
}

改正

int main()
{
    //2
	int* p2 = malloc(20);//相当于只申请了5*sizeof(int)大小的空间
    if(p2 == NULL)//如果p2是空指针,就直接返回
    {
        return;
    }
	for (int i = 0; i < 5; i++)//只访问5个int型数据
	{
		*(p2 + i) = i;
	}
    free(p2);
    p2 = NULL;
    return 0;
}

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

错误原因分析

int main
{
    //3
	int a = 10;//这个局部变量,是存放在栈区的
	int* p3 = &a;
	free(p3);//free只能对动态内存进行释放,动态内存开辟的空间是在堆区上
    //free这里会报错
    p3 = NULL;
    return 0;
}

改正

int main
{
    //3
	int a = 10;//这个局部变量,是存放在栈区的
	int* p3 = &a;
    p3 = NULL;
    return 0;
}

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

错误原因分析

int mian
{
    //4
	int* p4 = malloc(40);//相当于在堆区上申请了10*sizeof(int)大小的空间
	if (p4 != NULL)
	{
		for (int i = 0; i < 5; i++)
		{
			*p4 = i;
            p4++;  //++相当于p4 = p4 + 1 会改变p4的值
		}
	}
    else
    {
        return;
    }
	free(p4); //经过for循环,会把p4指针向后挪动5个int大小的空间
    //这个free会报错,非法访问内存
	p4 = NULL;
	
	return 0;
}

改正

int mian
{
    //4
	int* p4 = malloc(40);//相当于在堆区上申请了10*sizeof(int)大小的空间
	if (p4 != NULL)
	{
		for (int i = 0; i < 5; i++)
		{
			*(p4 + i) = i;//这个表达式不会改变p4的指向
		}
	}
    else
    {
        return;
    }
	free(p4); 
	p4 = NULL;
	
	return 0;
}

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

错误原因分析

int main()
{
    //5
	int* p5 = malloc(12);
	free(p5);
	free(p5);//这里重复释放了,算是非法访问

    return 0;
}

改正

int main()
{
    //5
	int* p5 = malloc(12);
	free(p5);
	p5 = NULL;

    return 0;
}

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

错误原因分析

int main()
{
	while (1)
	{
		int* ptr = malloc(100);//相当于一直在向内存申请空间,不释放
	}
	return 0;
}
//这个代码会让内存占用飙到100%,最终挂掉

改正

int main()
{
	while (1)
	{
		int* ptr = malloc(100);
        free(ptr);
	}
	return 0;
}

面试题

 1、

p只是函数的形参,更改p的值对str无影响,所以str仍然是空指针,对空指针解引用会报错。

void GetMemory(char *p)
{
    p = (char *)malloc(100);
}
void Test(void)
{
    char *str = NULL;
    GetMemory(str);
    strcpy(str, "hello world");
    printf(str);
}

2、

"hello world"是常量字符串,会存放在内存中 只读常量的内存空间处。

而p是数组,是函数内部创建的临时变量,出了函数会被销毁。

在GetMemory函数内部,p确实拿到了这个常量字符串首元素地址,但是出了函数,p会被销毁,str还是一个空指针。

char *GetMemory(void)
{
    char p[] = "hello world";
    return p;
}
void Test(void)
{
    char *str = NULL;
    str = GetMemory();
    printf(str);
}

3、返回栈空间地址的问题

函数栈空间,进入函数时创建,出了函数就会销毁。

而p1数组是函数内部创建的临时变量,出了函数会被销毁,在GetMemory函数内部,p1里面存放着首元素地址(即&p1[0])0x00fbf780,通过p1能找到首字符'h',但是出了GetMemory函数,函数栈帧会被销毁,p1这个地址确实还能找到,但是p1这个地址存放的内容已经变成随机值了。str在访问一块不属于我的内存空间,非法访问了。

char* GetMemory1(void)
{
	char p1[] = "hello world";//p1是局部变量,存的是首元素地址
    //出了函数之后,p就会被销毁,不能再拿着p的地址去访问,但实际上这块空间是归还给操作系统了,里面的内容是随机值
	return p1;
}
char* GetMemory2(void)
{
	char* p2 = "hello world";//p2是局部变量,存的是常量字符串的地址
	printf("%p\n", "hello world");
	return p2;
}
void Test(void)
{
	char* str1 = NULL; 
	char* str2 = NULL;
	str1 = GetMemory1();
	str2 = GetMemory2();
	printf(str2);
}
int main()
{
	Test();
	return 0;
}

分析:

  上图为,程序运行后p1数组的地址和对应值

上图为,程序运行后p2指针的地址和对应值。由此可以看出常量字符串的地址是0x004f8dd8。

上图为 数组p1的内存情况图,可以看出存放了 'h' 'e' 'l' 'l' 'o' ' ' 'w' 'o' 'r' 'l' 'd' '\0'。

 上图为 指针p2的内存情况图,可以看出存放的的确是 字符串常量函数的地址。

 

 上图为字符串常量的内存情况图。

 

 上图为出了GetMemory函数,数组p1的内存情况,可以看到空间被回收了,内容已经被改了。

 

上图为 str1和str2的地址和对应的值。

上图为 当GetMemory函数栈帧销毁时,具体内存情况 。

 

p2就可以正常打印出hello world了!因为函数返回的p2中存放的值是常量字符串的地址,str2拿到常量字符串的地址就可以正常访问常量字符串了!常量字符串在整个程序运行结束后才销毁!

 

原理同 下面这个程序 函数返回的是变量,无影响。这个具体原因涉及 函数栈帧的开辟 这一块知识点。

因为返回值会先存在eax寄存器里面,ret就能从eax寄存器中拿到这个变量值。

int test()
{
    int a = 10;
    return a;
}
int main()
{
    int ret = test();
    return 0;
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值