动态内存管理 后篇
- 🫅经典例题
- 🤦♂️题目1
- 🤦♂️题目2
- 🤦♂️题目3
- 🤦♂️题目4
- 🫅C/C++程序的内存开辟
前面的一篇文章动态内存管理 前篇,我们已经了解过了动态内存管理的相关信息,接下来,让我们来一起来踩“坑”。当然,今天的踩坑,是为了面试不踩坑哦
🫅经典例题
🤦♂️题目1
void GetMemory(char* p)
{
p = (char*)malloc(100);
}
void Test(void)
{
char* str = NULL;
GetMemory(str);
strcpy(str, "hello world");
printf(str);
}
int main()
{
Test();
return 0;
}
我们来找一找上面的代码有什么问题?
好了,不绕弯子了,来看看到底错了哪些地方
- GetMemory(str)中是传值,而不是传址。当出了GetMemory函数,p指向的地址发生了变化,但str依旧是NULL,然后就进入了strcpy函数,这样就形成了非法访问内存(NULL是操作系统的,我们用户没有权限访问)
- 在GetMemory函数内部,动态申请了内存,直到程序结束也没有释放内存,这样会导致内存泄漏
🐉🐉🐉🐉🐉
printf(str)???这样写也能行???为什么吗??
我们来看看下面的代码:
#include<stdio.h>
int main()
{
char ptr[] = "hehehe";
char* p = "hehehe";
printf("%s\n", ptr);
printf("%s\n", p);
printf("hehehe");
return 0;
}
//运行结果:
*****
hehehe
hehehe
hehehe
*****
纳尼??这是为什么呢?
在解释这个问题前,我们先想一下printf的第一个参数是什么?答案是:必须是指针。那就意味printf(“hehehe”)里面的“hehehe”不是我们所看到的那样,而是一个地址。那么它是谁的地址?下面的代码会给我们答案:
#include<stdio.h>
int main()
{
char* p = "hehehe";
printf("%p\n", "hehehe");
printf("%p\n", p);
return 0;
}
//运行结果:
*****
00AF7B54
00AF7B54
*****
奥,那我就明白了:ptr指针指向的是字符串的第一个字符,那么运行结果告诉我们,printf(“hehehe”)里面传的就是第一个字符的地址。
修改:
//修改后
// #include<stdio.h>
#include<stdlib.h>
void GetMemory(char** p) //二级指针接收
{
*p = (char*)malloc(100); //解引用就是一级指针
}
void Test(void)
{
char* str = NULL;
GetMemory(&str); //传址
strcpy(str, "hello world");
printf(str);
//释放空间
free(str);
str = NULL;
}
int main()
{
Test();
return 0;
}
🤦♂️题目2
#include<stdio.h>
#include<stdlib.h>
char* GetMemory(void)
{
char p[] = "hello world";
return p;
}
void Test(void)
{
char* str = NULL;
str = GetMemory();
printf(str);
}
int main()
{
Test();
return 0;
}
这个除了没有释放空间就没有其他的问题了吧。
但真相是这样吗??
让我们看看运行结果是什么吧
嗯??没释放空间也不会导致这种结果吧…
🐉🐉🐉🐉🐉
让我们来全面的分析一下吧:
- 关于函数返回信息的问题,这一类问题也叫作“返回栈空间地址的问题”:函数返回地址,地址信息存在,但函数内部的栈空间就还给操作系统了,操作系统可以把此空间作为别的变量的地址
- 函数动态申请空间,直到程序结束也没有释放掉空间,会导致内存泄漏
返回栈空间地址的问题???这是什么啊??
栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返
回地址等
那我们就明白了为什么叫做返回栈空间地址,那问题呢??
当函数调用结束(出了函数),在函数里面的空间就会返还给操作系统,我们用户没有操作权限。操作系统会把这个空间重新分配给其他变量,这样我们就不得而知了
修改:
#include<stdio.h>
#include<stdlib.h>
char* GetMemory(void)
{
char* p = "hello world"; //将第一个字符的地址传给p
//static char p[]="hello world"; //被static修饰的变量存放在数据段(静态区),
//数据段的特点是在上面创建的变量,直到程序结束才销毁
return p;
}
void Test(void)
{
char* str = NULL;
str = GetMemory();
printf(str);
//释放
free(str);
str = NULL;
}
int main()
{
Test();
return 0;
}
🤦♂️题目3
#include<stdio.h>
#include<stdlib.h>
void GetMemory(char** p, int num)
{
*p = (char*)malloc(num);
}
void Test(void)
{
char* str = NULL;
GetMemory(&str, 100);
strcpy(str, "hello");
printf(str);
}
int main()
{
Test();
return 0;
}
这个就很简单了,没有释放空间
修改:
#include<stdio.h>
#include<stdlib.h>
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;
}
🤦♂️题目4
#include<stdio.h>
#include<stdlib.h>
void Test(void)
{
char* str = (char*)malloc(100);
strcpy(str, "hello");
free(str);
if (str != NULL)
{
strcpy(str, "world");
printf(str);
}
}
int main()
{
Test();
return 0;
}
这个问题是啥?感觉没问题啊??不会是str没有置空吧?
哈哈,其实这个问题很隐蔽,不注意看是看不出来的
free的动作是将该内存块返还给操作系统,我们没有操作权限,故str是野指针,造成了非法访问内存
修改:
#include<stdio.h>
#include<stdlib.h>
void Test(void)
{
char* str = (char*)malloc(100);
strcpy(str, "hello");
free(str);
str = NULL; //在这里置空,也是为了符合下面判断的逻辑
//那么也不会进入if里面造成非法访问
if (str != NULL)
{
strcpy(str, "world");
printf(str);
}
}
int main()
{
Test();
return 0;
}
🫅C/C++程序的内存开辟
在在这里,终于把C语言中的动态内存管理的知识讲解完了。想必大家大概的框架是有的,但是对一些专业术语跟底层逻辑不是很清楚。比如:什么是堆区,什么又是栈区?内存中的每一个区域的作用是什么?等等
C/C++程序内存分配的几个区域:
- 栈区(stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结
束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是
分配的内存容量有限。 栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返
回地址等。 - 堆区(heap):一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。分
配方式类似于链表。 - 数据段(静态区)(static)存放全局变量、静态数据。程序结束后由系统释放。
- 代码段:存放函数体(类成员函数和全局函数)的二进制代码。字符串常量就在其中
- 实际上普通的局部变量是在栈区分配空间的,栈区的特点是在上面创建的变量出了作用域就销毁。
但是被static修饰的变量存放在数据段(静态区),数据段的特点是在上面创建的变量,直到程序
结束才销毁。所以生命周期变长。
码文不易,各位看官一键三连哦 💕💕💕
各位的鼓励与支持是我前进最大的动力