错误代码分析
- 动态内存常见错误如下:
- 对NULL指针的解引用操作
- 对动态开辟空间的越界访问
- 对非动态开辟内存使用free释放
- 使用free释放一块动态开辟内存的一部分
- 对同一块动态内存多次释放
- 动态开辟内存忘记释放(内存泄漏)
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;
}