内存泄漏:
void GetMemory(char* p)
{
p = (char*)malloc(100);
//free(p);
//p = NULL;
}
void Test(void)
{
char* str = NULL;
GetMemory(str);
strcpy(str, "hello world");
printf(str);
}
int main()
{
Test();
return 0;
}
上述代码中,主函数调用test()函数,创建一个空指针变量str,然后传值给函数GetMemory()函数,指针变量p接收,p也是空指针,malloc开辟100个字节的空间并将开辟空间的地址赋给p,然后离开GetMemory函数,局部变量p被销毁,内存中开辟的空间的地址并没有传给str也没有被释放,发生内存泄漏。 代码第11行将“hello world”拷贝到str所指向的内存空间中,NULL不是有效地址,并没有指向有效空间,强行拷贝发生对NULL的解引用,遍历NULL试图拷贝,遍历过程中会访问非法内存,程序在这里崩溃。
上述代码修改:
#include <string.h>
#include <stdlib.h>
void GetMemory(char** p)
{
*p = (char*)malloc(100);
}
void Test(void)
{
char* str = NULL;
GetMemory(&str);
if (str == NULL)
{
return 0;
}
strcpy(str, "hello world");
printf(str);
free(str);
str = NULL;
}
int main()
{
Test();
return 0;
}
另一种改正方法是将GetMenory中的p返回到Test函数中,使用str接收返回值,即接收动态内存开辟的地址。
返回栈空间地址(错误):
char* GetMemory(void)
{
char p[] = "hello world";
return p;
}
void Test(void)
{
char* str = NULL;
str = GetMemory();
printf(str);
}
int main()
{
Test();
return 0;
}
::: tip 代码分析:主函数调用Test函数,新建空指针str,调用GetMemory,创建p的数组,并将p的地址返回给Test函数中的str。数组p是局部范围内的数组,只能在函数GetMemory内部使用,离开该函数后数组p生命周期结束,str不能通过存储的地址找到p指向的字符串。 ::: ::: warning 局部变量创建在栈区,离开创建的范围就会销毁,返回栈空间的地址是有问题的,此时该地址指向的不再是创建的局部变量,有可能是其他数据,会造成非法访问数据的问题。 :::
返回静态区的地址(正确):
int* Test()
{
int a = 0; //栈区
return &a;
}
int main()
{
int* p = Test();
*p = 20;
return 0;
}
如果使用static修饰局部变量数组时,static修饰的变量数组放在静态区,生命周期变长,离开函数后并不销毁,*p = 20就可以正确赋值。
返回堆区的地址(正确):
int* Test()
{
int p = malloc(100);
return &p;
}
int main()
{
int* ptr = Test();
return 0;
}
上述代码中,malloc开辟的内存空间是在堆区的,离开Test函数后,变量p被销毁,但malloc开辟的内存空间并不会销毁,p将该空间的地址返回给ptr,通过ptr可以访问到该空间。 堆区创建的空间只有free命令才可以回收。
非法访问内存
int Hi()
{
int* p;
*p = 20;
return 0;
}
上述代码中指针p没有初始化,p指向的空间是随机值,对随机值赋值容易出现非法访问内存的问题。p没有指向有效的内存空间,称为野指针。
void GetMemory(char** p,int num)
{
*p = (char*)malloc(num);
}
void Test(void)
{
char* str = NULL;
GetMemory(&str,100);
strcpy(str, "hello");
printf(str);
//改进
//free(str);
//str = NULL;
}
int main()
{
Test();
return 0;
}
上述代码中,malloc开辟的空间地址传给 * p, * p就是void str,即新开辟的内存空间地址放在str中,是可以执行strcpy的操作的。但是在开辟空间使用完毕后,没有释放该空间,会导致内存泄漏。
释放后的空间被使用:
#include <stdlib.h>
#include <string.h>
void Test(void)
{
char* str = (char*)malloc(100);
strcpy(str, "hello");
free(str);
//改进
//str = NULL;
if (str != NULL)
{
strcpy(str, "world");
printf(str);
}
}
int main()
{
Test();
return 0;
}
上述代码中if判断语句不起作用,原因是free(str)后,str变为野指针。 ::: tip Test函数创建变量str存储malloc开辟的100字节内存空间,将“hello\0”拷贝到str指向的空间,即在malloc开辟的内存空间中,free(str)的效果是将malloc开辟的内存空间释放掉,虽然str中仍存有该空间地址,但是无法访问该地址。free命令不会将str置为空指针,if判断为真,再将“world”拷贝到str中去,执行的结果是该内存空间中的“hello”被“world”覆盖。 但是,str指向的空间已经被free命令释放,该空间已不再属于str,再对该内存空间进行拷贝和打印操作,就涉及非法访问内存。 :::
总结: C/C++程序的内存开辟方式 ::: tip 栈区(stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。 堆区 ( heap ):一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收。分配方式类似于链表。 数据段(静态区)(static):存放全局变量、静态数据。程序结束后由系统释放。 代码段︰存放函数体(类成员函数和全局函数)·的二进制代码。 :::