动态内存管理(c语言)


我们通常开辟空间的方式

cs 复制代码
int val =20; //大小为4个字节
char arr[10] ={0} //开辟出一块连续的空间且大小为10

但是上面开辟空间方式的特点

1.空间开辟大小是固定的

2.数组在声明得时候,必须指定数组得长度,它所需要得内存在编译时分配

但是以上的方式不能满足所有情况,有时候我们需要空间的大小在程序运行的时候才能知道,

那数组编译的方式就不能满足了,你那就可以使用动态内存

一.堆区,栈区,静态区

1.栈区:

1)遵循自动分配与释放原则

当函数被调用时,函数内的局部变量,函数的参数灯会在栈上分配空间,函数执行完毕后,这些内存会自动被释放

2)后进先出原则

比如说嵌套函数的时候,内层函数的局部变量会先入栈,当内存函数返回后,其局部变量先出栈,外层函数才能继续执行并访问其自身的局部变量

3)空间有限。如果在栈上分配了过多的内存,可能会导致栈溢出,程序出现错误甚至崩溃

2.堆区

动态分配与释放:堆区的内存分配和释放由程序员手动控制,通过函数malloc,calloc,realloc等函数可以在栈上申请空间,使用完毕后,必须通过free函数释放内存,否则会导致内存泄漏,会一直占用系统资源,知道程序借宿

内存空间较大:堆区的内存空间相对栈区来说要大得多,其大小通常只限于计算机的物理内存和虚拟内存的大小。这使得堆区适用于存储大量的数据或动态生成的数据结构,如数组,链表,树等

分配灵活:

根据程序的实际需求动态申请任意大小的内存块

碎片化问题:

由于堆区的内存分配和释放时动态,经过多次申请和释放后,可能会导致内存碎片化

,内存利用率降低

3.静态区:

全局生命周期:

静态区存储的是全局变量和静态变量,这些变量在程序整个生命周期都有效,直到程序结束

初始化规则

未初始化的全局变量和静态变量会自动初始化为0和空指针,初始化的变量就按照初始化的进行

作用域的限制

全局变量作用域是整个程序,可以在如何函数中访问和修改;而静态局部变量的作用域仅限于定义它的函数内部,但它在函数多次调用后会保持其值不变

二.动态内存函数

1.malloc函数

头文件 #include<stdlib.h>

这个函数向内存申请一块连续可用的空间,并指向这块空间的指针

如果开辟成功,则返回一个指向该块空间的指针

如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要检查

返回值为void*,所以malloc函数并不知道开辟空间的类型,具体在使用的时候由操作者自己决定

函数格式

函数参数的意思:size表示要分配的内存块的大小,单位是字节

cs 复制代码
void*malloc(size_t size)

2.free函数

头文件 #include<stdlib.h>

free函数用来释放动态开辟的内存

1.如果参数ptr指向的空间不是动态开辟的,那free函数的行为是未定义的。

2.如果参数ptr是NULL指针,则函数什么事都不做

cs 复制代码
void free (void* ptr);

malloc函数和free函数使用如下

cs 复制代码
#include<string.h>
#include<stdio.h>
#include<errno.h>
#include<stdlib.h>
int main()
{
	//向内存申请空间,且未初始化
	int* p = (int*)malloc(20);
	//判断空间是否开辟成功
	if (p == NULL)
	{
		//判断错误的原因
        //strerror函数是用来判断错误的原有
		printf("%s\n", strerror(errno));
		return 1;
	}
	//使用
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		//这俩种形式等价
		//*(p + i) = i + 1;
		//printf("%d ", *(p + i));
		//p[i] = i + 1;
		//printf("%d ", p[i]);
	}
	//释放
	free(p);
	p = NULL;
	return 0;
}

注意:

1.把p被free释放后,p仍然指向某一个地址,需要对其设置为空指针,避免出现空指针

2.若malloc函数开辟空间失败,则会返回空指针,所以需要对malloc函数的返回值进行检查

如果malloc函数开辟的空间太大,则会开辟失败

3.calloc函数

头文件:#include<stdio.h>

calloc也是用来开辟动态空间的,但它会把这块空间每个字节初始化为0

参数1:num表示要分配的元素个数,参数2:表示每个元素的大小

cs 复制代码
void* calloc (size_t num, size_t size);

calloc函数和free函数的使用

cs 复制代码
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>

int main()
{
	//开辟动态空间
	int* p = (int*) calloc(10, sizeof(int));
	//判断是否开辟成功
	if (p == NULL)
	{
		printf("%s", strerror(errno));
		return 1;
	}
	//使用
	int i = 0;
	//进行空间的遍历
	for (i = 0; i < 10; i++)
	{
		printf("%d ", p[i]);
	}
	return 0;
	//释放内存
	free(p);
	p = NULL;
}

程序运行


4.realloc函数

头文件:#include<stdlib.h>

realloc函数开始实现对动态开辟空间大小的调整

格式

参数1:ptr是一个指向先前通过malloccallocrealloc函数分配的内存块的指针,也就是要调整内存的地址

参数2:size表示重新分配后内存块的大小

返回值:realloc函数返回后,返回值为调整之后的空间起始地址

cs 复制代码
void* realloc (void* ptr, size_t size);
realloc函数的应用
cs 复制代码
#include<stdio.h>
#include<stdlib.h>
%include<errno.h>



int main()
{
	//开辟动态空间
	int* p = (int*) calloc(10, sizeof(int));
	//判断是否开辟成功
	if (p == NULL)
	{
		printf("%s", strerror(errno));
		return 1;
	}
//重新分配内存使其能容纳80个字节的空间,成功使用
//realloc函数后,原来由calloc函数开辟的空间会被回收
//所以不需要对p进行释放,如果释放,则会导致程序崩溃
	int* pa = realloc(p, 80);
	if (pa == NULL)
	{
		printf("%s", strerror(errno));
		return 1;
	}
    //使用
	int i = 0;
	for (i = 0; i < 20; i++)
	{
		*(pa+i) = i + 1;
		printf("%d ",*(pa+i));

	}
    //释放
	free(pa);
	pa = NULL;
}

程序运行:

realloc在调整内存空间有俩种情况

1.原有空间之后有足够大的空间

2.原有空间之后没有足够大的空间

若为情况1

使用realloc函数后,直接在原有的空间后直接扩展,最终成为一块连续空间,且原有的空间数据不发生变化

若为情况2

原有空间之后没有足够的空间,如果要扩展的话,在原有的堆空间另找一个合适大小的连续空间来使用,这时函数返回的是一个新空间的地址

情况1:在原来的空间直接进行扩展

情况2:没有找到足够的空间,在另一处又开辟了一个空间

三.动态内存中常见的错误

cs 复制代码
void test()
 {
 int *p = (int *)malloc(INT_MAX/4);
 *p = 20;
 free(p);
 }

这段代码有什么问题呢👆

没有对malloc函数是否成功开辟空间进行判断,如果p为NULL,对NULL的解引用是错误的

cs 复制代码
void test()
 {
     int i = 0;
     int *p = (int *)malloc(10*sizeof(int));
     if(NULL == p)
     {
         exit(EXIT_FAILURE);
     }
     for(i=0; i<=10; i++)
     {
         *(p+i) = i;
     }
     free(p);
}

这段代码有什么问题呢👆

对动态开辟的空间进行越界访问,且最后p没有置为NULL

cs 复制代码
void test()
 {
     int a = 10;
     int *p = &a;
     free(p);//ok?
 }

对非动态开辟的内存进行free,free之后也要把指针p指向的地址设置为NULL

cs 复制代码
void test()
 {
     int *p = (int *)malloc(100);
     p++;
     free(p);
 }

因为p++,所以p并没有在动态内存的起始地址,所以free仅释放了一部分动态内存

cs 复制代码
void test()
 {
 int *p = (int *)malloc(100);
 free(p);
 free(p);、
 }

对同一块动态内存多次释放

cs 复制代码
void test()
 {
 int *p = (int *)malloc(100);
 if(NULL != p)
 {
 *p = 20;
 }
 }
 int main()
 {
 test();
 while(1);
 }

使用过后并没有对malloc开辟的动态空间进行free,free之后,要把指针p指向的空间设置为NULL


四.经典的笔试题

cs 复制代码
void GetMemory(char *p)
 {
     p = (char *)malloc(100);
 }
 void Test(void)
 {
     char *str = NULL;
     GetMemory(str);
     strcpy(str, "hello world");
     printf(str);
 }

请问这段代码错误出在哪👆

1.调用GetMemory的时候,str的传参为值传递,p是str临时变量,所以再GetMemory函数内部将动态内存的地址存放在p中,不会影响str。所以在GetMemory函数返回之后

,str中依然是空指针。strcpy函数就会调用失败原因是对NULL的解引用操作,程序会崩溃。

2.malloc是否成功申请空间没有检验

3.GetMemory函数内容malloc申请的空间没有机会释放

,造成了内存泄漏

cs 复制代码
char *GetMemory(void)
 {
     char p[] = "hello world";
     return p;
 }
 void Test(void)
 {
     char *str = NULL;
     str = GetMemory();
     printf(str);
 }

请问这段代码错误出在哪👆

这是一个栈空间的问题GetMemory函数内部创建的数组是临时的,虽然返回了数组的起始地址给了str,但是数组的内存出了GetMemory函数就被回收了,而str依然保存了数组的起始地址,这时如果使用str,str就是野指针,也就是说访问值是随机的

cs 复制代码
void GetMemory(char **p, int num)
 {
     *p = (char *)malloc(num);
 }
 void Test(void)
 {
     char *str = NULL;
     GetMemory(&str, 100);
     strcpy(str, "hello");
     printf(str);
 }

请问这段代码错误出在哪👆

1.没有用free释放malloc开辟的空间,造成内存泄漏

2.并没有对malloc是否开辟出空间进行检验

cs 复制代码
void Test(void)
 {
     char *str = (char *) malloc(100);
     strcpy(str, "hello");
     free(str);
     if(str != NULL)
     {
         strcpy(str, "world");
         printf(str);
     }
 }

请问这段代码错误出在哪👆

把free把指针所指向的空间释放后,并没有把指针置为NULL,形成了悬空指针

因为虽然 str 指针本身没有被赋值为 NULL,但其指向的内存已经被释放,再使用这个指针会导致未定义行为,如果继续执行,可能会导致程序崩溃产生不可预料的结果

五.柔性数组

在c99中,结构体中最后一个元素允许是未知大小的数组,这就是柔性数组

柔性数组的创建

第一种方式👇

cs 复制代码
typedef struct st_type
 {
 int i;
 int a[0];//柔性数组成员
}type_a;

第二种方式👇

cs 复制代码
typedef struct st_type
 {
 int i;
 int a[];//柔性数组成员
}type_a;

柔性数组的特点

1.结构体柔性数组成员前必须至少一个其它成员

2.sizeof返回的大小不包含柔性数组的大小

3.包含柔性数组成员的结构体应该用malloc函数进行内存的动态分配,并且分配的大小应该大于结构体的大小,以适应柔性数组的预期大小

1.sizeof返回的大小不包含柔性数组的大小

cs 复制代码
#include<stdio.h>
struct s
{
	int i;
	int a[0];//柔性数组成员
};


int main()
{
	printf("%d ", sizeof(struct s));
	return 0;
}

程序运行

3.使用malloc函数进行柔性数组的空间分配

cs 复制代码
struct S
{
	int n;
	char c;
	int arr[];


};

int main()
{
	//柔性数组创建空间,+号前头的是不包含柔性数组大小的空间,后边是创建了
	//元素个数为10的柔性数组
	struct S* ps = (struct S*)malloc(sizeof(struct S) + 10 * sizeof(int));
	if (ps == NULL)
	{
		printf("%p\n", strerror(errno));
		return 1;
	}
	//柔性数组的使用
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		ps->arr[i] = i + 1;
		printf("%d ", ps->arr[i]);


	}
	//释放
	free(ps);
	ps = NULL;
}

代码运行

结语:

限于水平,本篇文章不足之处在所难免,还望读者见谅,如有问题,请大家指正下。

相关推荐
Horacek4 分钟前
《C++ 实现生成多个弹窗程序》
java·开发语言·c++·学习·算法
licy__7 分钟前
python的matplotlib实现数据分析绘图
开发语言·python
zero_one_Machel16 分钟前
leetcodeQ76最小覆盖子串
java·开发语言·算法
luky!22 分钟前
算法----查找列表中元素的最小值和最大值
开发语言·python·算法
_.Switch33 分钟前
Django中的URL配置与动态参数传递(多种方法比较)
开发语言·数据库·后端·python·django·sqlite·自动化
尘浮生35 分钟前
Java项目实战II基于微信阅读网站小程序的设计与实现(开发文档+数据库+源码)
java·开发语言·数据库·spring boot·微信·微信小程序·小程序
飞滕人生TYF42 分钟前
java Collections 详解
java·开发语言·python
Tang Paofan1 小时前
C++ constexpr
开发语言·c++
又菜又爱玩的东哥2 小时前
【C语言】四种方法交换两个数(免费复制)
c语言·开发语言
未命名冀2 小时前
微服务day09
java·开发语言·数据库