前言
使用动态内存开辟可以让我们对内存空间的使用更加灵活,但如果使用不当就可能让整个程序崩溃。下面通过4道经典面试题来测试一下你以前使用动态内存的方法是否正确,答案在文末公布,如果每道题你都能讲出个所以然来,恳请指出我有没有什么讲得不到位的地方。
4道经典面试题
请问以下4段代码运行Test函数后分别有什么结果
(一)
void CreatSpace(char*p)
{
p=(char*)malloc(100);
}
void Test()
{
char*str=NULL;
CreatSpace(str);
strcpy(str,"hello world");
printf(str);
}
(二)
char *CreatSpace(void)
{
char p[]="hello world";
return p;
}
void Test(test)
{
char*str=NULL;
str=CreatSpace();
printf(str);
}
(三)
void CreatSpace(char**p,int num)
{
*p=(char*)malloc(num);
}
void Test(void)
{
char *str=NULL;
CreatSpace(&str,NULL);
strcpy(str,"hello world");
printf(str);
}
(四)
void Test(void)
{
char*str=(char*)malloc(100);
strcpy(str,"hello ");
if(str!=NULL)
{
strcpy(str,"world");
printf(str);
}
}
动态内存函数的介绍
malloc
函数原型
void* malloc(size_t size);
malloc函数是向内存申请一块连续可用的空间,并返回这块空间地址
· 如果空间开辟失败会返回一个空指针,所以malloc的返回值一定要做检查
· 返回的指针类型是void型,使用者要根据自己需求进行强制类型转换
· 如果size为0,malloc的行为是未定义的,取决于编译器
free
函数原型
void free(void *ptr)
free函数用来释放和回收动态开辟的内存
· 如果ptr指向的空间不是动态开辟的,则free函数的行为是未定义的
· 如果ptr是空指针,则函数什么都不做
calloc
函数原型
void* calloc(size_t num,size_t size);
calloc函数用于为num个大小为size的元素开辟空间
· 如果空间开辟失败会返回一个空指针,所以calloc的返回值也要做检查
· 返回的指针类型是void型,使用者要根据自己需求进行强制类型转换
·会把申请的空间的每个字节初始化为0,这是与malloc函数的区别
realloc
函数原型
void* realloc(void*ptr,size_t size);
realloc函数用于将ptr指向的空间调整为size个字节
· 返回的指针类型是void型,使用者要根据自己需求进行强制类型转换
· 空间调整时存在3种情况:
①原有空间后有足够大的空间
则拓展的内存直接就在原有内存之后追加空间,原有数据不发生变化
②原有空间之后没有足够的空间
在堆区再寻找一块适合大小的连续空间来使用,并返回这块新空间的地址,同时会把原来空间的数据拷贝过来,并释放原有空间
③找不到足够大的空间,返回空指针,因此realloc的返回值也要做检查
现在给你一段代码,看看有什么缺陷,当然了,你最好在看完后面6种常见的动态内存错误后再返回来看这段代码
int main(void)
{
int*ptr=(int*)malloc(100);
if(ptr!=NULL)
{
//业务处理
}
else
{
return 0;
}
//现在要拓展空间
ptr=(int*)realloc(ptr,1000);
//业务处理
free(ptr);
ptr=NULL;
return 0;
}
这段代码的缺陷在于:如果空间拓展失败,返回了空指针,则ptr被置为NULL,导致我们找不到原来malloc函数开辟的空间了,但我们原来的空间还没有释放掉,从而造成内存泄漏
正确的空间拓展方式
int main(void)
{
int*ptr=(int*)malloc(100);
if(ptr!=NULL)
{
//业务处理
}
else
{
return 0;
}
//现在要拓展空间
int*p=NULL;
p=(int*)realloc(ptr,1000);
if(p!=NULL)
{
ptr=p;
}
else
{
//内存拓展失败的处理
}
//业务处理
free(ptr);
ptr=NULL;
return 0;
}
c/c++内存开辟的补充
这里补充一下c/c++中程序内存区域划分
6种常见的动态内存错误
①对NULL指针的解引用操作
void test(void)
{
int *p=(int*)malloc(4);
*p=2023; /如果内存开辟失败,p为空指针,解引用就会出错,因此使用前要判断指针是否为空/
free(p);
}
②对动态开辟空间的越界访问
void test(void)
{
int *p=(int*)malloc(10*sizeof(int));
if(NULL==p)
{
return;
}
int i=0;
for(i=0;i<=10;++i) /i=10时,越界/
{
*(p+i)=i;
}
free(p);
}
③对非动态开辟的空间使用free释放
void test(void)
{
int a=2023;
int*p=&a;
free(p); /出错/
}
④使用free释放一块动态开辟内存的一部分
void test(void)
{
int*p=(int*)malloc(16);
p++;
free(p); /此时p已经不在指向动态开辟空间的起始位置/
}
⑤对同一块内存多次释放
void test(void)
{
int*p=(int*)malloc(100);
free(p);
free(p); /重复释放,错误/
}
⑥动态内存忘记释放(内存泄露)
void test(void)
{
int *p=(int*)malloc(100);
//业务代码
/没有使用free函数释放动态开辟的空间/
}
int main(void)
{
test();
return 0;
}
内存泄露是一种非常严重的错误,很容易造成程序的崩溃
动态内存释放的方式有两种:
· 使用free函数释放
· 程序结束
有些程序是一天24小时都在运行的,如京东这类软件等,如果存在内存泄漏,程序每次调用这个函数时都会泄漏一点内存,而程序又一直运行,等到内存被耗尽时程序就崩溃了。
所以使用动态内存的一个好习惯是:
· 开辟后要判断是否开辟成功
· 使用完后要及时将空间释放
· 空间释放完后将指针置为空
答案与分析
下面来看看前面4道面试题
(一)程序出错
Test函数在调用CreatSpace函数时进行的是值传递(是将str作为参数,而不是传地址过去),CreatSpace函数开辟空间后将地址传给p,但p在CreatSpace函数调用结束后被销毁了,所以str依旧是空指针,strcpy将一个字符串拷贝给空指针,程序出错,同时动态开辟的空间没有释放,造成内存泄漏。
(二)打印出乱码
str虽然获得了CreatSpace中p的地址,但该地址指向的空间在CreatSpace函数调用结束后便还给系统了,此时的str放的是一个野指针,对野指针指向的内容打印便是乱码
(三)正常打印"hello world"
但在使用之前没有判断动态开辟是否成功,同时动态开辟的空间没有释放,造成内存泄漏
(四)打印"world"
但动态开辟的空间没有释放,从而造成内存泄漏
结语
相信通过学习本篇文章,你对动态内存开辟的使用有了一个更好的理解。如果文章中有什么不对的地方,恳请指正,最后不要忘记点赞收藏加关注哦!