C语言动态内存管理

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文

目录

1.存在动态内存分配的原因

2.动态内存函数

2.1malloc和free

2.2calloc

2.3realloc

3.常见的动态内存错误

3.1对空指针的解引用操作

3.2对动态开辟空间的越界访问

3.3对非动态开辟内存使用free释放

3.4使用free释放一块动态开辟内存的一部分

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

3.6动态开辟内存忘记释放(内存泄露)

4.柔性数组

4.1定义

4.2特点

4.3使用

4.4柔性数组的优势

1.存在动态内存分配的原因

之前我们掌握的内存开辟方法有:

int num = 0;  //在栈空间开辟四个空间
char arr[5] = {0};  //在栈空间开辟一段连续的空间

但是上述的方式有两个特点:

  1. 空间开辟大小是固定的
  2. 数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配

但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候才能知道,那数组的编译时开辟空间的方式就不能满足了。

这时候就需要动态存开辟

2.动态内存函数

malloc,calloc,realloc,free这些函数操作的空间都是在堆区

2.1malloc和free

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

  • 如果开辟成功,则返回一个指向开辟好空间的指针
  • 如果开辟失败,则返回一个NULL指针,因此malloc返回值一定要检查
  • 返回值的类型是void*,需要使用者自己强制类型转换
  • 如果参数size为0,malloc的行为是标准未定义的,取决于编译器分配

free专门做动态内存的释放和回收

  • 如果参数ptr指向的空间不是动态开辟的,那free函数的行为是未定义的,报错
  • 如果参数ptr是NULL指针,则函数什么事都不做

malloc和free函数都声明在stdlib.h的头文件中

//malloc函数(申请)
void* malloc (size_t size);
//返回值为泛型指针,调用时需要强制类型转换
//参数是需要开辟的字节数
//free函数(释放)
void free (void* ptr);

举例:

#include <stdio.h>
#include <stdlib.h> //malloc函数的调用
#include <string.h> //strerror函数的调用
#include <errno.h>  //errno的使用
int main() {
	//动态内存分配
	int* p = (int*)malloc(40);  //INT_MAX:自定义类型整型最大
	if (p == NULL) {
		//打印运行错误信息
		//strerror会返回指向错误消息字符串的指针
		//errno是记录系统的最后一次错误代码,类型是int型
		//在程序运行出错时,一般会将errno变量赋一个整数值,不同的值表示不同的含义
		//可以通过查看该值推测出错的原因
		printf("%s\n",strerror(errno));
		return 1;
	}
	//使用
	int i = 0;
	for (i = 0; i < 10; i++) {
		*(p + i) = i;
	}
	for (i = 0; i < 10; i++) {
		printf("%d ", *(p + i));
	}
	return 0;
        //释放内存
        //free(p);
        //p = NULL;
        //释放后p仍指向那个地址,但确实已经还给系统,此时p为野指针
        //因此在free之后理应将p指向NULL
        //没有free
,当程序退出的时候系统会自动回收内存空间
}

运行正确结果:

 内存分配失败返回错误信息(当开辟空间很大时):

2.2calloc

函数声明:

void* calloc (size_t num, size_t size);
  • 函数的功能是为num个大小为size的元素开辟一块空间,并且把空间的每个字节初始化0
  • 与函数malloc的区别只在于calloc会在返回地址之前把申请的空间的每个字节初始化为全0

举例:

int main() {
	int* p = calloc(10, sizeof(int));
	if (p == NULL) {
		printf("%s\n", strerror(errno));
		return 1;
	}
	int i = 0;
	for (i = 0; i < 10; i++) {
		printf("%d ", *(p + i));
	}
	free(p);
	p = NULL;
	return 0;
}

运行内存结果:

2.3realloc

realloc的优势:

  • realloc函数的出现让动态内存管理更加灵活
  • realloc可以做到对动态开辟内存大小的调整

函数声明:

void* realloc (void* ptr, size_t size);
  1. ptr是要调整的内存首地址
  2. size是调整后的新大小
  3. 返回值为调整之后的内存起始位置
  4. 这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间
  5. realloc在调整内存空间的时候存在两种情况:
  • 情况1:原有空间之后没有足够大的空间
  • 情况2:原有空间之后有足够大的空间

举例:

int main() {
	int* p = (int*)malloc(40);
	if (p == NULL) {
		printf("%s\n",strerror(errno));
		return 1;
	}
	int i = 0;
	for (i = 0; i < 10; i++) {
		* (p + i) = i;
	}
	//扩容
        //realloc(NULL,40)等价于malloc(40)
	//不能直接赋给p,如果内存找不到这么大的空间,将会返回NULL
	//如果直接赋给p,将会丢失原来的空间,可能会造成内存泄露
	int* ptr = (int*)realloc(p, 500);\
	if (ptr == NULL) {
		printf("%s\n", strerror(errno));
		return 1;
	}
	p = ptr;
	ptr = NULL;
	//使用
	for (i = 0; i < 10; i++) {
		printf("%d ", *(p + i));
	}
	free(p);
	p = NULL;
	return 0;
}

运行示例:

3.常见的动态内存错误

3.1对空指针的解引用操作

代码示例:

int main() {
	//此时内存没有这么大的空间分配,返回了NULL
	//p拿到返回值后没有判断,直接解引用,就会出问题
	int* p = (int*)malloc(INT_MAX);
	*p = 10;
	return 0;
}

调试结果:

3.2对动态开辟空间的越界访问

代码示例:

int main() {
	int* p = (int*)malloc(40);
	if (p == NULL) {
		printf("%s\n", strerror(errno));
		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;
}

运行结果:

3.3对非动态开辟内存使用free释放

代码示例:

int main() {
	int a = 0;
        //p指向的内存不是动态开辟出来的
	int* p = &a;         
	free(p);
	return 0;
}

运行结果:

3.4使用free释放一块动态开辟内存的一部分

代码示例

int main() {
	int* p = (int*)malloc(40);
	if (p == NULL) {
		printf("%s\n", strerror(errno));
		return 1;
	}
	int i = 0;

	for (i = 0; i < 5; i++) {
		*p = i;
                //p的值改变                  
		p++;
	}
	//使用
	for (i = 0; i < 3; i++) {
		printf("%d ", *(p + i));
	}
        //释放一部分         
	free(p);
	p = NULL;
	return 0;
}

运行结果:

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

int main() {
	int* p = (int*)malloc(40);
	//释放一次,p没有赋为空指针
	free(p);
	//p = NULL;
	//释放第二次,仍指向那个地址
	free(p);
	return 0;
}

代码报错提示:

3.6动态开辟内存忘记释放(内存泄露)

代码示例:

int main() {
	int i = 0;
	int* p = (int*)malloc(20);
	if (p == NULL) {
		printf("%s\n", strerror(errno));
		return 1;
	}
 	scanf("%d", &i);
	if (i == 1) {
		//可能还会有一些代码
		//初看可能并没有错误
		//但在这个if语句中,如果直接返回而没有释放内存
		//就极有可能导致内存泄露
		//也是最容易出的错误
		return 0;
	}
	free(p);
	p = NULL;
	return 0;
}

4.柔性数组

4.1定义

C99 中,结构中的最后一个元素允许是未知大小的数组,这就叫做[柔性数组]成员

//第一种写法,有些编译器会报错无法编译
typedef struct data {
	int num;
	char str[0];//柔性数组成员
}data1;
//第二种写法,更普遍
typedef struct data {
	int num;
	char str[];//柔性数组成员
}data2;

4.2特点

  • 结构中的柔性数组成员前面必须至少一个其他成员
  • sizeof 返回的这种结构大小不包括柔性数组的内存

代码示例:

int main() {
	int sz = sizeof(struct data1);
	printf("%d\n",sz);
	return 0;
}

运行结果:

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

代码示例:

int main() {
	struct data1* p = (struct data1*)malloc(sizeof(struct data1) + 40);
	if (p ==NULL) {
		printf("%s\n",strerrno(errno));
		return 1;
	}
	return 0;
}

4.3使用

代码示例:

int main() {
        //这样柔性数组成员就相当于获得了40个字节大小的空间
	struct data1* p = (struct data1*)malloc(sizeof(struct data1) + 40);
	if (p ==NULL) {
		printf("%s\n",strerror(errno));
		return 1;
	}
	int i = 0;
	p->num = 10;
	for (i = 0; i < p->num - 1; i++) {
		p->str[i] = 'a' + i;
	}
	p->str[p->num - 1] = '\0';
	printf("%s\n", p->str);
	free(p);
	p = NULL;
	return 0;
}

运行结果:

4.4柔性数组的优势

以上代码也可以设计成另一种形式

代码示例:

struct Data {
	int num;
	int* p;
};

int main() {
	struct Data* pd = (struct Data*)malloc(sizeof(struct Data));
	if (pd == NULL) {
		printf("%s\n", strerror(errno));
		return 1;
	}
	int i = 0;
	pd->num = 10;
	pd->p = (int*)malloc(pd->num * sizeof(int));
	if (pd->p == NULL) {
		printf("%s\n", strerror(errno));
		return 1;
	}
	for (i = 0; i < pd->num; i++) {
		pd->p[i] = i;
	}
	for (i = 0; i < pd->num; i++) {
		printf("%d ", pd->p[i]);
	}
	//需要释放两次,忘记的可能性增大
	free(pd->p);
	free(pd);
	pd = NULL;
	//pd->p不用置空,pd置空后pd->p就找不见了
	return 0;
}

运行截图:

上述两种方式都可以使用,但第一种方式的实现有两个好处:

  1. 方便内存释放
  2. 有利于访问速度

连续的内存有益于提高访问速度,也有益于减少内存碎片

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

求索1024

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值