当前位置:网站首页>动态内存管理
动态内存管理
2022-07-24 11:42:00 【Kkkkvvvvvxxx】
目录
前言
我们知道变量的存储方式分为两种,一种是静态存储另一种是动态存储;而全局变量是存储在静态区的,局部变量以及函数形参等是存储到栈区的,动态开辟的空间则是存储到堆区的。所以为了解决空间开辟是固定的这种情况,我们就引入了动态内存开辟
1.动态内存函数的介绍
对内存的动态分配是通过系统提供的库函数来实现的,主要有malloc、calloc、free、realloc
1.1malloc和free
C语言为我们提供了一个动态内存开辟函数malloc,这个函数是想内存申请一块连续可用的空间,并返回一个指向这块空间的地址。
void *malloc(size_t size)
- 如果开辟成功,则返回一个指向开辟好空间的指针
- 如果开辟失败,则返回一个NULL指针
- 因为返回值是void *所以malloc并不知道开辟空间的类型
- 若size_t的参数为0,malloc的行为是未定义的,取决于编译器
C语言提供一个专门用来释放回收动态开辟的内存,函数如下:
void free(void *p)
- 释放指针p所指向的动态空间,使这部分空间可以被其他变量使用
- 如果p指向的空间不是动态开辟的,那free的行为是未定义的
- 如果p是NULL指针,则free什么也不做
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{
int num;
scanf("%d", &num);
//开辟num*4个字节大小的空间
int* ptr = NULL;
ptr = (int*)malloc(num * sizeof(int));
if (ptr != NULL)
{
for (int i = 0; i < num; i++)
{
*(ptr + i) = i;
}
}
free(ptr);
ptr = NULL;
return 0;
}
上述代码就是动态内存开辟与释放的基本使用,而在释放晚ptr所指向的空间后,需要重新将ptr置成NULL指针,否则ptr仍然指向那部分已经释放的空间,从而将会变成野指针!
1.2calloc
void *(size_t num.size_t size)
- 函数的功能是为num个字节大小为size的元素开辟一块空间,并把空间中的每个字节初始化为0;
- 除了初始化为0,剩下的与malloc一致。
int main()
{
int* ptr = NULL;
ptr = (int*)calloc(10, 4);
if (ptr != NULL)
{
//使用空间
}
free(ptr);
ptr = NULL;
return 0;
}

1.3realloc
realloc使得内存管理更加灵活,可以对空间大小进行调整
void *realloc(void *p,size_t size)
- p是需要调整的内存地址,size是调整后的大小
- 返回值是调整后内存空间的起始地址
- 如果重新分配不成功则返回NULL
而因为realloc是调整空间,所以在将空间变大时,就存在两种情况:
- 原有空间后边有足够大的空间,则直接进行追加,原来空间的数据不发生变化;
- 原有空间后边没有足够的空间,则此时会在内存中寻找一块合适的空间进行开辟使用,返回一个新的内存地址。
#include<errno.h>
int main()
{
int* ptr = NULL;
ptr = (int*)calloc(10, 4);
if (ptr == NULL)
{
printf("%s", strerror(errno));
return 1;
}
int* p = NULL;
p = realloc(ptr, 80);
if (p == NULL)
{
printf("%s", strerror(errno));
return 1;
}
ptr = p;
free(ptr);
ptr = NULL;
return 0;
}
上述代码就是realloc的应用,而在使用时我新建了一个指针变量p;如果我们用ptr接收的话,万一创建失败返回NULL时,此时ptr就被置成NULL其之前的40个字节的空间也就找不到了,所以用p变量进行接收就是防止这种情况!
2.常见的动态内存错误
2.1对NULL指针进行解引用操作
void test()
{
int* p = (int*)malloc(INT_MAX / 4);
*p = 20;
free(p);
}
如果*p的空间申请失败返回NULL在对其解引用时是错误的!
2.2对动态开辟的空间进行越界访问
#include<errno.h>
int main()
{
int* p = (int*)malloc(40);
if (p == NULL)
{
printf("%s", strerror(errno));
return 1;
}
for (int i = 0; i <= 10; i++)
{
p[i] = i;
}
free(p);
p = NULL;
return 0;
}
即使是动态开辟的空间也是有限的,不可以进行越界访问!
2.3对非动态开辟内存进行free释放
int main()
{
int* p;
int a = 20;
p = &a;
free(p);
p = NULL;
return 0;
}
free只能用在动态开辟的空间中去,否则随意使用于非动态内存时程序会崩溃!
2.4使用free释放动态开辟内存的一部分
#include<errno.h>
int main()
{
int* p = (int*)malloc(1000);
if (p == NULL)
{
printf("%s", strerror(errno));
return 1;
}
for (int i = 0; i < 5; i++)
{
*p = i;
p++;
}
free(p);
p = NULL;
return 0;
}
此时,p不再指向动态开辟空间的起始位置,所以不能进行释放,否则程序会崩溃;因此只释放动态内存空间的一部分是不被允许的!
2.5对同一块内存进行多次释放
int main()
{
int* p = (int*)malloc(1000);
if (p == NULL)
{
printf("%s", strerror(errno));
return 1;
}
free(p);
free(p);
return 0;
}
此时代码崩溃,所以上述情况也是不被允许的!
2.6动态开辟的空间忘记释放
int* test()
{
int* p = malloc(100);
if (p == NULL)
{
printf("%s", strerror(errno));
return 0;
}
return p;
}
int main()
{
int* ret = test();
//忘记释放空间
return 0;
}
上述代码在调用完函数后忘记释放空间,此时会造成内存泄漏,即在内存中找不到该存储单元了;
2.7笔试题目
1️⃣
void GetmMemory(char* p)
{
p = (char*)malloc(100);
}
int main()
{
char* str = NULL;
GetMemory(str);
strcpy(str, "hello world");
printf(str);
return 0;
}
分析:函数GetMemory开辟内存空间,而函数调用结束后,p被销毁,但是p开辟的空间仍然存在,而main函数中无法得到这块空间的起始地址,str仍然是一个空指针,所以进行拷贝字符串后,程序会崩溃。
如何更改呢?
void GetmMemory(char** p)
{
*p= (char*)malloc(100);
}
...
...
GetMemory(&str);
我们只需进行址传递即可,所以需要使用二级指针进行接收,而对p解引用就可以找到str,此时相当于对str接收了所开辟的内存空间;此时程序就可以正常运行!
2️⃣
char* GetMemory()
{
char p[] = "hello world";
return p;
}
int main()
{
char* str = NULL;
str=GetMemory();
printf(str);
}
分析:在函数调用时为数组p开辟内存空间,当函数调用结束后开辟的内存空间随之被销毁,继而返回p的地址,而此时p指向的空间已经被操作系统收回,不再属于p,所以str接收这个地址打印后,所打印的内容是不确定的,即str是个野指针!
3️⃣
void GetMemory(char** str, int num)
{
*str = (char*)malloc(num);
}
int main()
{
char* str = NULL;
GetMemory(&str, 100);
strcpy(str, "hello world");
printf(str);
}
分析:此函数可以正常编译打印,原理同第一题一样,唯一不足之处在于未释放空间,存在内存泄漏!
4️⃣
int main()
{
char* str = (char*)malloc(100);
strcpy(str, "hello");
free(str);
if (*str != NULL)
{
strcpy(str, "world");
printf(str);
}
return 0;
}
分析:开辟空间后释放,释放后str并不是NULL,所以满足if条件,将world复制进str中就会形成非法访问··
3.C内存开辟
- 栈区:在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数结束后这些存储单元被自动释放;栈区主要存放局部变量、函数参数、返回数据、返回地址等;
- 堆区:一般动态内存在此进行开辟,一般由程序员进行释放存取;
- 静态区:存放全局变量、静态数据,程序结束后由系统收回。
4.柔性数组
在结构体中最后一个元素允许是大小未知的数组,这种数组就叫做柔性数组。
struct Data
{
int i;
int arr[0];
};
如上所示,结构体中这种代码就是柔性数组;
4.1柔性数组的特点
- 结构体中柔性数组前面必须要有一个其他成员
- sizeof返回的这种结构体的大小不包括柔性数组
- 包含柔性数组成员的结构体需要用malloc进行动态内存开辟,并且分配的内存应该大于结构体的大小,以适应柔性数组的大小
int main()
{
struct Data data;
printf("%d", sizeof(struct Data));
return 0;
}
此时返回4个字节;
int main()
{
struct Data *data = (int*)malloc(sizeof(struct Data) + 40);
printf("%d", sizeof(struct Data));
return 0;
}
此时仍然返回4个字节;
4.2柔性数组的使用
1️⃣
struct Data
{
int i;
int arr[0];
};
int main()
{
struct Data* p = (struct Data*)malloc(sizeof(struct Data) + 40);
p->i = 100;
if (p == NULL)
{
printf("%s", strerror(errno));
return 1;
}
for (int i = 0; i < 10; i++)
{
p->arr[i] = i;
printf("%d ", p->arr[i]);
}
printf("\n");
//扩容
struct Data* ptr = realloc(p, sizeof(struct Data) + 80);
if (ptr == NULL)
{
printf("%s", strerror(errno));
return 1;
}
p = ptr;
ptr = NULL;
for (int i = 0; i < 20; i++)
{
p->arr[i] = i;
printf("%d ", p->arr[i]);
}
free(p);
p=NULL;
return 0;
}
我首先对柔性数组申请了40个字节的空间,然后对其进行扩容至80个字节的空间;
2️⃣
我们不妨先看一段代码,将结构体中数组改成指针是否可行呢?
struct Data
{
int i;
int* arr;
};
int main()
{
struct Data* ps = (struct Data*)malloc(sizeof(struct Data));
if (ps == NULL)
{
printf("%s", strerror(errno));
return 1;
}
ps->i = 100;
ps->arr = (int*)malloc(40);
if (ps->arr == NULL)
{
printf("%s", strerror(errno));
return 1;
}
for (int i = 0; i < 10; i++)
{
ps->arr[i] = i;
printf("%d ", ps->arr[i]);
}
printf("\n");
//扩容
int* pt = NULL;
pt = realloc(ps->arr, 80);
if (pt == NULL)
{
printf("%s", strerror(errno));
return 1;
}
ps->arr = pt;
pt = NULL;
for (int i = 0; i < 20; i++)
{
ps->arr[i] = i;
printf("%d ", ps->arr[i]);
}
free(ps->arr);
ps->arr = NULL;
free(ps);
ps = NULL;
}
我将结构体中的数组变成一个指针,首先我先将结构体开辟到堆区,然后在对指针进行malloc开辟动态内存空间,之后对其进行扩容使用!
总结:
1柔性数组方便内存释放,只需要进行一次内存释放即可,因为开辟空间是连续进行开辟的;
2.有利于提高运行速率…
5.void指针类型
void*也是一种指针类型,他的意思是 “指向空类型”或“不指向确定的类型”,他不可以进行解引用以及±整数的操作,这一特点我在以往的博客中也有提到过,所以在上面malloc、calloc等函数的应用时,我都会进行一次强制类型的转换从而赋给相关指针!
Ending
在本篇博客中我介绍了动态内存开辟的相关函数,以及比较冷门的柔性数组,以及几种常见的动态内存的错误开辟,而在动态内存开辟时经常由于忘记释放内存从而造成内存泄漏,所以需要时常注意这一点!
边栏推荐
- 哈希——15. 三数之和
- CCF 1-2 question answering record (2)
- iMeta观点 | 短读长扩增子测序是否适用于微生物组功能的预测?
- cgo+gSoap+onvif学习总结:9、go和c进行socket通信进行onvif协议处理
- Is there any charge for PDF processing? impossible!
- Cgo+gsoap+onvif learning summary: 9. Go and C conduct socket communication and onvif protocol processing
- 哈希——1. 两数之和——有人白天相爱,有人夜里看海,有人力扣第一题都做不出来
- HCIP MGRE实验 第三天
- Share the typora tool
- Jmeter-While控制器
猜你喜欢

Share the typora tool

GCC的基本用法

One week's wonderful content sharing (issue 13)
![08 [AIO programming]](/img/a6/156cb97e653190c76f22c88b758fef.png)
08 [AIO programming]

L1-059 ring stupid bell

Paging query of employee information of black maredge takeout

Shengxin weekly issue 37
](/img/fd/e12f43e23e6ec76c2b44ce7813e204.png)
运算放大器 —— 快速复苏笔记[贰](应用篇)

Record a garbage collection and analysis of gceasy

NFT digital collection system construction - app development
随机推荐
Hash - 18. Sum of four numbers
How to use a third party without obtaining root permission topic: MIUI chapter
What is cloud native? Why is cloud native technology so popular?
Robot framework official tutorial (I) getting started
什么是云原生,云原生技术为什么这么火?
Operational amplifier - Notes on rapid recovery [II] (application)
Install MariaDB columnstore (version 10.3)
MOS管 —— 快速复苏应用笔记(壹)[原理篇]
Nacos permissions and databases
运算放大器 —— 快速复苏笔记[贰](应用篇)
安装jmeter
Semaphore details
Ctfshow ThinkPHP topic 1
Detailed explanation of stat function
哈希——18. 四数之和
iMeta观点 | 短读长扩增子测序是否适用于微生物组功能的预测?
How to choose sentinel vs. hystrix current limiting?
Linked list - Sword finger offer interview question 02.07. linked list intersection
L1-049 天梯赛座位分配
Shell script