驾驭代码的无形疆界:动态内存管理揭秘
一叶之秋1412 2024-08-20 14:05:04 阅读 75
目录
1.:为什么要有动态内存分配
2.malloc和free
2.1:malloc
2.2:free
3.calloc和realloc
3.1:calloc
3.1.1:代码1(malloc)
3.1.2:代码2(calloc)
3.2:realloc
3.2.1:原地扩容
3.2.2:异地扩容
3.2.3:代码1(原地扩容)
3.2.3:代码2(异地扩容)
4:常见的动态内存的错误
4.1:对NULL指针的解引用操作
4.2:对动态开辟空间的越界访问
4.3:对非动态开辟的内存使用free释放
4.4:使用free释放动态开辟内存的一部分
4.5:对同一块动态内存多次释放
4.6:对动态内存忘记释放(内存泄漏)
5:动态内存经典笔试题分析
5.1:题目1(问运行test函数有什么样的结果)
5.1.1:改法1:传二级指针
5.1.2:改法2:以return的形式返回
5.2:题目2(问运行test函数有什么样的结果)
5.3:题目3(问运行test函数有什么样的结果)
5.3.1:修改后
5.4:题目4(问运行test函数有什么样的结果)
5.4.1:修改后
6:C/C++程序的内存开辟区域划分
嘿嘿,uu们, 今天咱们来详细剖析动态内存管理,好啦,废话不多讲,开干!
1.:为什么要有动态内存分配
通过之前的学习,我们已经掌握的内存开辟方式有
<code>//在栈区上开辟四个字节.
int value = 25;
//在栈空间上开辟40个字节的连续空间.
int arr[10] = {0};
但是上面的开辟空间的方式有两个特点:
1.空间开辟大小是固定的.
2.在C99之前,数组在声明的时候,必须指定数组的长度,数组空间⼀旦确定了大小不能调整
但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候
才能知道,那么数组的编译时开辟空间的⽅式就不能满足了.
那么C语言引入了动态内存开辟,让我们能够自己开辟和释放空间,这样子的话,就相对来讲比较灵活了.
2.malloc和free
2.1:malloc
C语言提供了一个动态内存开辟的函数, 我们首先来看看官方的解释
<code>void* malloc (size_t size);
malloc这个函数向内存空间申请一块连续可用的空间,并返回指向这块空间的指针.
1:
如果开辟成功,则返回⼀个指向开辟好空间的指针.
2:
如果开辟失败,则返回⼀个NULL指针,
因此malloc的返回值⼀定要做检查。
3:返回值的类型是
void*,
所以malloc函数并不知道开辟空间的数据类型,因此具体在使⽤的时候由使用者自己来决定.
4:如果参数
size
为0,malloc的⾏为是标准是未定义的,取决于编译器.
2.2:free
当我们自己向内存空间申请了空间后,在使用完后要对其进行释放与回收,那么C语言提供了另外一个函数free,专门用来针对动态内存的释放与回收的.
free函数⽤来释放动态开辟的内存。
如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。
如果参数 ptr 是NULL指针,那么该函数什么事都不做.
PS:malloc和free在使用时需要包含stdlib.h头文件.
了解了malloc和free函数后,我们来看个例子.
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
int main()
{
//申请一块空间,用来存放10个整型*/
int* p = (int*)malloc(10 * sizeof(int));
if (p == NULL)
{
perror("malloc");
return 1;
}
//使用
int i = 0;
for (i = 0; i < 10; i++)
{
//p[i];
*(p + i) = i;
}
for (i = 0; i < 10; i++)
{
printf("%d ", p[i]);
}
/*
* 由于p指针还会指向原本的地址,此时如果不赋为空指针的话, 那就是野指针了
* 释放ptr所指向的动态内存
*/
free(p);
p = NULL;
return 0;
}
3.calloc和realloc
3.1:calloc
C语言还提供了一个函数叫做calloc,calloc函数也可以用来进行动态内存分配,我们来看看其原型
<code> void* calloc (size_t num, size_t size);
第一个参数是:元素的个数.
第二个参数是:元素占据内存空间的大小.
该函数的功能是为num个大小为size的元素开辟一块空间,并且把空间的每个字节都初始化为0.与函数malloc的区别只在于calloc会在返回地址之前把申请的空间的每个字节初始化为全0.
了解了calloc函数后,我们来看个例子
3.1.1:代码1(malloc)
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
int main()
{
//申请一块空间,用来存放10个整型*/
int* p = (int*)malloc(10 * sizeof(int));
if (p == NULL)
{
perror("malloc");
return 1;
}
//使用
int i = 0;
for (i = 0; i < 10; i++)
{
//p[i];
*(p + i) = i;
}
for (i = 0; i < 10; i++)
{
printf("%d ", p[i]);
}
/*
* 由于p指针还会指向原本的地址,此时如果不赋为空指针的话, 那就是野指针了
* 释放ptr所指向的动态内存
*/
free(p);
p = NULL;
return 0;
}
3.1.2:代码2(calloc)
<code>#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
int main()
{
//申请一块空间,用来存放10个整型*/
int* p = (int*)calloc(10,4);
if (p == NULL)
{
perror("calloc");
return 1;
}
//使用
int i = 0;
for (i = 0; i < 10; i++)
{
//p[i];
*(p + i) = i;
}
for (i = 0; i < 10; i++)
{
printf("%d ", p[i]);
}
/*
* 由于p指针还会指向原本的地址,此时如果不赋为空指针的话, 那就是野指针了
* 释放ptr所指向的动态内存
*/
free(p);
p = NULL;
return 0;
}
3.2:realloc
realloc函数的出现让动态内存管理更加灵活有时候我们会发现过去申请的空间过小或者过大了,那么为了合理的分配内存,我们就会对内存的大小做出灵活的调整.那么realloc函数就可以对动态开辟的内存进行调整.
第一个参数ptr为要调整的起始内存地址.第二个参数size为调整之后新大小.返回值为调整之后的内存起始地址.realloc函数在调整原内存空间大小的基础上,还会将原来内存中的数据拷贝到新的空间.realloc在调整内存空间存在两种情况
First:原有空间有足够大的空间那么会原地扩容.Second:原有空间之后没有足够的空间,那么会异地扩容.
3.2.1:原地扩容
当是情况1的时候,要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发生变化。
3.2.2:异地扩容
当是情况2的时候,原有空间之后没有⾜够多的空间时,扩展的方法是:在堆空间上另找⼀个合适大小的连续空间来使用。这样函数返回的是⼀个新的内存地址.
综合上述的两种情况,那么realloc函数的使用就要略微注意一些.
3.2.3:代码1(原地扩容)
<code>#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
int main()
{
int* p = calloc(10, sizeof(int));
if (NULL == p)
{
perror("molloc");
return 1;
}
printf("\n");
/*
* 原有空间足够则在原有的空间进行扩展
* 如果原有空间后面的空间不足够,则realloc函数会在堆区重新开辟一块足够的空间
*/
int* ptr = (int*)realloc(p, 20 * sizeof(int));
if (ptr != NULL)
{
p = ptr;
}
return 0;
}
3.2.3:代码2(异地扩容)
<code>#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
int main()
{
int* p = calloc(10, sizeof(int));
if (NULL == p)
{
perror("molloc");
return 1;
}
printf("\n");
/*
* 原有空间足够则在原有的空间进行扩展
* 如果原有空间后面的空间不足够,则realloc函数会在堆区重新开辟一块足够的空间
*/
int* ptr = (int*)realloc(p, 1000 * sizeof(int));
if (ptr != NULL)
{
p = ptr;
}
return 0;
}
4:常见的动态内存的错误
4.1:对NULL指针的解引用操作
<code>#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
int main()
{
//不对返回值作判断,就可能使用NULL指针,对空指针进行解引用
int* p = (int*)malloc(INT_MAX);
*p = 20;
return 0;
}
上面的代码,由于动态开辟的内存过大,那么因此开辟空间是失败的,那么malloc开辟空间失败的话,就会返回NULL指针.那么此时再对其进行解引用操作的话,就会发生对NULL指针的解引用操作.
4.2:对动态开辟空间的越界访问
<code>#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
int main()
{
int* p = calloc(10, sizeof(int));
if (NULL == p)
{
perror("calloc");
return 1;
}
int i = 0;
//赋值
for (i = 0; i <= 10; i++)
{
p[i] = i;
}
//打印
for (i = 0; i <= 10; i++)
{
printf("%d ", *(p + i));
}
//释放
free(p);
p = NULL;
return 0;
}
上述代码则发生对动态开辟空间的越界访问,在开辟空间的时候,是只开辟了数量为10个,大小为4字节的空间,但是在赋值和打印的时候,对第11个空间进行了访问,那么则发生了对动态开辟空间的越界访问.
4.3:对非动态开辟的内存使用free释放
<code>#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
int main()
{
int a = 10;
int* p = &a;
//对非动态内存开辟进行释放
free(p);
p = NULL;
return 0;
}
上面的代码则发生对非动态开辟的内存使用了free释放,指针变量p指向的是变量a,指向的区域是栈区,而动态开辟的内存是在堆区,因此发生了对非动态开辟的内存使用了free释放.
4.4:使用free释放动态开辟内存的一部分
<code>#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
int main()
{
int* p = calloc(10, sizeof(int));
if (p == NULL)
{
perror("calloc");
return 1;
}
int i = 0;
//赋值
for (i = 0; i < 5; i++)
{
*p = i;
p++;
}
// 0 1 2 3 4 0 0 0 0
//此时p指向第六个位置
free(p);
p = NULL;
return 0;
}
上述的代码是发生了对动态开辟的内存只释放了一部分.
4.5:对同一块动态内存多次释放
<code>#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
int main()
{
int* p = malloc(40);
if (NULL == p)
{
return 1;
}
free(p);
free(p);
return 0;
}
上述代码则发生了对同一块动态内存多次释放,对于动态开辟的内存只能够释放一次,这就好比,我们日常去住酒店,当我们退了酒店房间后,是不能够再一次退酒店房间的.
4.6:对动态内存忘记释放(内存泄漏)
<code>#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
void test()
{
int* p = (int*)malloc(40);
if (3)
{
return;
}
free(p);
p = NULL;
}
int main()
{
test();
return 0;
}
总结:忘记释放不再使用的动态开辟的空间会造成内存泄漏
切记:动态开辟的空间一定要正确释放.
5:动态内存经典笔试题分析
5.1:题目1(问运行test函数有什么样的结果)
<code>#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void GetMemory(char* p)
{
//这个形参出了函数之后就会被销毁外加返回类型为void
//malloc开辟的空间没有释放,因此发生了内存泄漏
p = (char*)malloc(100);
}
void Test(void)
{
char* str = NULL;
GetMemory(str);
//发生对空指针的解引用操作,导致了程序崩溃
strcpy(str, "hello world");
printf(str);
}
int main()
{
Test();
return 0;
}
5.1.1:改法1:传二级指针
<code>//改法1
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.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;
}
5.1.2:改法2:以return的形式返回
<code>#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
//改法2,以return返回的形式
char* GetMemory()
{
char* p = (char*)malloc(100);
return p;
}
void Test(void)
{
char* str = NULL;
str = GetMemory();
strcpy(str, "hello world");
printf(str);
//开辟了动态内存之后要进行释放并且置为NULL,因为此时str还会指向原本的地址,如果不放置为NULL,那就是野指针了
free(str);
str = NULL;
}
int main()
{
Test();
return 0;
}
5.2:题目2(问运行test函数有什么样的结果)
<code>#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
char* GetMemory(void)
{
//为局部变量,出了函数就被销毁了
char p[] = "hello world";
//返回的是地址值
return p;
}
void Test(void)
{
char* str = NULL;
//str非法访问了空间,此时str为野指针
str = GetMemory();
printf(str);
}
int main()
{
Test();
return 0;
}
5.3:题目3(问运行test函数有什么样的结果)
<code>#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.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;
}
5.3.1:修改后
<code>#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
//修改后
void GetMemory(char** p, int num)
{
*p = (char*)malloc(num);
}
void Test()
{
char* str = NULL;
GetMemory(&str, 100);
strcpy(str, "hello");
printf(str);
free(str);
str = NULL;
}
int main()
{
Test();
return 0;
}
5.4:题目4(问运行test函数有什么样的结果)
<code>#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void Test()
{
char* str = (char*)malloc(100);
strcpy(str, "hello");
//释放了开辟动态内存空间后,指针str还是会指向原本的值,因此此时str为野指针
free(str);
if (str != NULL)
{
//对野指针的非法访问
strcpy(str, "world");
printf(str);
}
}
int main()
{
Test();
return 0;
}
5.4.1:修改后
<code>#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
//修改
void Test()
{
char* str = (char*)malloc(100);
strcpy(str, "hello");
/*
* 释放了开辟动态内存空间后, 指针str还是会指向原本的值, 因此此时str为野指针,
释放了以后要对其进行置NULL
*/
free(str);
str = NULL;
if (str != NULL)
{
strcpy(str, "world");
printf(str);
}
}
int main()
{
Test();
return 0;
}
6:C/C++程序的内存开辟区域划分
1.栈区(stack):在执⾏函数时,函数内局部变量的存储单元都可以在栈上创建,函数执⾏结束时这些存储单元⾃动被释放。栈内存分配运算内置于处理器的指令集中,效率很⾼,但是分配的内存容量有限。 栈区主要存放运行函数⽽分配的局部变量、函数参数、返回数据、返回地址等。 2.堆区(heap):⼀般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。分配方式类似于链表。 3. 数据段(静态区):存放全局变量、静态数据。程序结束后由系统释放。 4. 代码段:存放函数体(类成员函数和全局函数)的⼆进制代码。
好啦,uu们,动态内存管理这部分滴详细知识博主就讲到这里啦,如果uu们觉得博主讲的不错的话,请动动你们滴小手给博主点点赞,你们滴鼓励将成为博主源源不断滴动力,同时也欢迎大家来指正博主滴错误~
声明
本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。