C语言动态内存管理及柔性数组满满干货

在这里插入图片描述

一、为什么存在内存分配

1、数组的缺点:

开辟空间的大小被固定
数组开辟多少空间不缺点,有时候要程序运行时才知道
如果开辟太大空间就会造成空间浪费,并且不能通过手动释放空间
在函数内创建的数组,在函数运行结束后才会把空间释放掉
数组的长度不能在函数程序运行的过程中进行扩充或缩小

例如:

int main ()
{
    int a =0;//4byte
    int arr[10];//40byte
   return 0;
}

小结:

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

2.动态内存分配

a.计算机系统中几个内存区域

1)栈区:在栈里存放我们定义的局部变量,函数,形参,
2)堆区:堆注意是通过动态分配的存储空间。
3)静态区(全局区):在全局区里存储一下全局变量和静态变量
在这里插入图片描述

二、动态内存函数的

1.malloc

malloc用来开辟动态内存空间

void *malloc(size_t size)

这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针。
如果开辟成功,则返回一个指向开辟好空间的指针。
如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。
返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。
如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器。

2.free

c语言提供了free函数
free函数专门用来回收和释放动态内存空间
具体如下:

 void free(void*ptr);

如果参数 ptr 指向的空间不是动态开辟的(不是malloc,calloc,realloc和动态内存的),
那free函数的行为是未定义的
如果参数 ptr 是NULL指针,则函数什么事都不做

通过上面的学习我们来用一下malloc和free函数把!

#include<stdio.h>
#include<stdlib.h>
int main()
{
	int num = 0;

	int* ptr = NULL;
	scanf("%d", &num);
	ptr = malloc(num * sizeof(int));
	//初始化
	//判断一下malloc是否开辟失败
	if (ptr != NULL)
	{
		//开辟成功
		for (int i = 0; i < num; i++)
		{
			*(ptr + i) = i;
		}
	}
	free(ptr);//如果ptr是空指针那么,free函数什么也不干
	       // 如果ptr不等于空指针就释放malloc开辟的空间
	       //free函数不会改变ptr的地址
	ptr = NULL;//内存已经释放了,为了不让ptr不是野指针,所以给ptr赋值空指针;
	return 0;
}

3.问题

1.问题来了free怎么知道要释放多少空间?
答:free找到malloc存放的开辟空间大小数值。至于通过上面方法,要学到c++才知道呵呵哈哈哈。
2.怎样才会开辟失败。
答:例如开辟40亿个字节,开辟很大就会开辟失败。

举个哈哈的例子(帮助大家理解)
你呢,跟你girlfriend前期 相处非常好,你把女友电话记得牢牢的,有助于交流。
但是,有一天 你女朋友看不上你了,就free§【跟你分手了]
但是你§还死皮赖脸的记住你前女友的号码(动态开辟空间的地址),随时可能打电话骚扰她,并且找到她,影响她的生活,说明你很危险(访问该空间内容,并修改,该指针很危险)
这时候,你前女友为了你能断开念想(其实保护自己==维护程序安全)
你前女友 给你当头一棒(爆头),让你失忆了。 (p = NULL)

4.calloc

void * calloc(size_t num, size_t size);
  1. c语言还提供一个与malloc类似的函数, 在malloc基础上,给空间初始为0;
  2. 参数的意思就是 开辟num个大小为size的元素。
  3. 开辟成功后返回空间首地址
  4. 开辟失败就返回NULL;
#include<stdio.h>
#include<stdlib.h>

int main()
{
	int* p =(int*) calloc(10, sizeof(int));
	if (NULL != p)
	{
		//开辟成功
		//使用空间
	}
	free(p);
	p = NULL;
	return 0;
}
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main()
{
	int* p = (int*)calloc(10, sizeof(int));
	if (p == NULL)
	{
		printf("%s\n", strerror(errno));//如果开辟失败则打印错误
	}
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d", *(p + i));//0000000000
	}
	free(p);
	p = NULL;

	return 0;
}

5. 问题

1.以后要用malloc和calloc函数?
答:你要初始化就calloc,不初始就malloc。

6.realloc

realloc函数的出现让动态内存管理更加灵活。
有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的时候内存,
我们一定会对内存的大小做灵活的调整。那 realloc 函数就可以做到对动态开辟内存大小的调整。 函数原型
如下:

void * realloc(void *memblock ,size_t size);
  1. ptr 是要调整的内存地址
  2. size 调整之后新大小
  3. 返回值为调整之后的内存起始位置。

7.realloc 调整内存空间有两种情况

  1. 原有空间之后有足够大的空间
  2. 原有空间之后没有足够大的空间
  3. 在上面的基础上,没有找到一块够大的空间,开辟失败
    在这里插入图片描述
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main()
{
	int* p = (int*)calloc(10, sizeof(int));
	if (p == NULL)
	{
		printf("%s\n", strerror(errno));//如果开辟失败则打印错误
	}
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		*(p + i) = i;
	}
	int* pp = (int*)realloc(p, 20 * sizeof(int));//防止增容失败,返回NULL,用临时指针
	if (NULL != pp)
	{
		//增容成功、判断pp 
		p = pp;
	  }
	  else
	  {
	    return -1;
	  }
	// 初始化增容后的空间
	for (i = 10; i < 20; i++)
	{
		*(p + i) = i;
	}
	for (i = 0; i < 20; i++)
	{
		printf("%d ", *(p + i));//0 1 2 3 4 5 ~ 19
	}
	free(p);
	p = NULL;

	return 0;
}

8.常见的内存错误

  • 对空指针解应用
int main()
{
	int* p = (int*)malloc(40);
	//如果开辟失败,就是对空指针解引用
 
	*p = 0;
 free(p);
 p=NULL;
	return 0;
}
int main()
{
	int* p = (int*)malloc(40);
	//如果开辟失败,就是对空指针解引用
   // 所以要进行判断一下
	if (p == NULL)
	{
		return -1;
	}
	*p = 0;
    free(p);
    p=NULL;
	return 0;
}
  • 对动态开辟的空间进行越界的访问
int main()
{
	int* p = (int*)malloc(200);
	//如果开辟失败,就是对空指针解引用
   // 所以要进行判断一下
	if (p == NULL)
	{
		return -1;
	}
	for(int i = 0 ;i<80;i++)
	{
	   *(p+i)=i;
	}
	
    free(p);
    p=NULL;
	return 0;
}
  • 对非动态内存空间使用free函数
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

int main()
{
	int a = 0;
	int* p = &a;
	free(p);                // 直接奔溃

	return 0;
}

  • 使用free释放一块动态开辟内存的一部分
int main()
{
	int* p = (int*)malloc(40);
	p++;
	free(p);//           程序崩溃
	p == NULL;
	return 0;
}

在这里插入图片描述

  • 多次释放
int main()
{
	int* p = (int*)malloc(40);
	
	free(p);
	free(p);
	p == NULL;
	return 0;
}

解决方案:

int main()
{
	int* p = (int*)malloc(40);
	
	free(p);
	p==NULL;
	free(p);//p是空指针,所以free函数什么也不干;
	p == NULL;
	return 0;
}
  • 动态内存忘记释放(内存泄漏)

动态内存有两种释放的方式
1.主动free
2.程序退出的时候,申请的空间也会被没收

int main()
{
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		return -1;
	}
	// 使用后
	// 忘记释放
   getchar();//如果这个程序一直挂着,那么40个字节就一直存在
            // 
	return 0;
}

如果是循环啥的,有可能内存就被一点点用光

几道金典面试题

题目1、

#include<stdio.h>
#include<string.H>
void GetMemory(char* p)
{
	p = (char*)malloc(100);
}
void test(void)
{
	char* str = NULL;
	GetMemory(str);
	strcpy(str, "hello");
	printf(str);
}

int main()
{
	test();
	return 0;
}

这组代码在调用strcpy函数时,发生断言;
首先进入到test函数里,先将str置空指针,
GetMemory (char*p),p来接收str,一级指针传递给一级指针,是值传递,所以str是p的一份临时拷贝,形参的改变不会影响实参,
调用完malloc后返回开辟空间的首地址给p,p的改变不会改变str
所以此时str还是空指针
进入strcpy后,因为str是NULL,strcpy会调用assert函数,所以遇到空指针就会报错(断言);
小结:
导致程序崩溃,并且内存泄漏
内存泄漏,已经没有地址记录malloc开辟的空间,这块动态内存
不能够释放,除非程序结束时;

题目2、

char* GetMemory(void)
{
	char p[] = "hello";
	return p;
}
void test(void)
{
	char* str = NULL;
	str = GetMemory();
	printf(str);
}

这道题主要的问题是:返回栈空间地址问题
由于p指向的这块数组是局部的,所以当返回p地址时,GetMeomery空间销毁,这时候p就是野指针,那么把p赋值给str,str也是野指针,str指向的值不确定,就好像接下来,要调用printf函数
需要在栈帧上压栈,给printf开辟一块空间,也就是说,之前给GetMemory的那块空间,已经被printf 覆盖了,所以,str指向的值就是一个随机的。
小结:
str是一个野指针,指向的内容不确定,
所以printf 打印出来的内容也不确定,随机值,有可能是hello!!!

题目3、

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

题目3与题目1,差不多,一个传地址,一个传值,这道题注意就是
内存泄漏,动态内存用完后,没用及时释放空间
解决
在printf后加代码free函数;

题目4、

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

空间被释放
str非法访问
虽然打印出来是hello,但是
它本质就是错的,!!!!!!!!!!!!!!

九、 c/c++程序的内存开辟

在这里插入图片描述

  • 栈区(stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些
    存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有
    限。 栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。
  • 堆区(heap):一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。分配方式类似
    于链表。
  • 数据段(静态区)(static)存放全局变量、静态数据。程序结束后由系统释放。
  • 代码段:存放函数体(类成员函数和全局函数)的二进制代码。

被static修饰的局部变量,我的理解是,把局部变量放到了静态区,然后,改变了生命周期,但是作用域;

十、柔细数组

也许你从来没有听说过柔性数组(flexible array)这个概念,但是它确实是存在的。 C99 中,结构中的最
后一个元素允许是未知大小的数组,这就叫做『柔性数组』成员。

typedef struct st_type
{
	int i;
	int a[0];
	//int a[];或者这样
}; type_a;

柔性数组的特点

结构中的柔性数组成员前面必须至少一个其他成员。 sizeof 返回的这种结构大小不包括柔性数组的内存。 包含柔性数组成员的结构用malloc
()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应 柔性数组的预期大小。

例如:

typedef struct st_type
{
	int i;
	int a[0];
	//int a[];或者这样
}; type_a;
printf("%d\n", sizeof(type_a));//输出4

这是一块包含柔性数组的结构体声明,并创建了一个结构体变量,这结构也存在内存对齐,并且在sizeof计算的时候,结构体大小不包含柔性数组的大小。

代码一

#include<stdio.h>
#include<errno.h>
#include<stdlib.h>

struct type
{
	int i;
	int a[];//假如有10个元素
};
int main()
{
	struct type* p = (struct type*)malloc(sizeof(struct type) + sizeof(int) * 10);
	if (NULL == p)
	{
		printf("%s\n", strerror(errno));

		return -1 ;
	}

	//开辟成功
	else
	{
		p->i = 100;
		for (int i = 0; i < 10; i++)
		{
			p->a[i] = i;
		}for (int i = 0; i < 10; i++)
		{
		printf("%d ",p->a[i]);
		}
	}
	// 增容一下
	struct type* ps = (struct type*)realloc(p, sizeof(struct type) + sizeof(int) * 20);
  if (NULL == ps)
  {
	  printf("增容失败\n");
	  return -1;
   }
  else
  {
	  p = ps;
  }

  //使用
  // 、、
  //释放
  free(p);
  p = NULL;

	return  0;
}

代码二

#include<stdio.h>
#include<errno.h>
#include<stdlib.h>
struct type
{
	int i;
	int* a;
};
int main()
{
	struct type * ps =(struct type*)malloc(sizeof(struct type));
	ps->i = 100;
	ps->a = (int*)malloc(10 * sizeof(int));//维护动态内存地址
	for (int i = 0; i < 10; i++)
	{
		ps->a[i] = i;
	}
	//维护动态内存地址
	for (int i = 0; i < 10; i++)
	{
	   printf("%d ",ps->a[i]);

	}
	//a空间不够需要调整了
	int* ptr = (int*)realloc(ps->a, sizeof(int) * 20);
	if (ptr == NULL)
	{
		printf("增容失败\n");
	}
	else
	{
		ps->a = ptr;
	}
	//使用
	//、、
	//释放
	free(ps->a);
	ps->a = NULL;
	free(ps);
	ps == NULL;

	return 0;
}

简单的画两张图方便理解
在这里插入图片描述

第一个好处是:方便内存释放
如果我们的代码是在一个给别人用的函数中,你在里面做了二次内存分配,并把整个结构体返回给用户。用
户调用free可以释放结构体,但是用户并不知道这个结构体内的成员也需要free,所以你不能指望用户来发
现这个事。所以,如果我们把结构体的内存以及其成员要的内存一次性分配好了,并返回给用户一个结构体
指针,用户做一次free就可以把所有的内存也给释放掉。

第二个好处是:这样有利于访问速度 连续的内存有益于提高访问速度,也有益于减少内存碎片。(其实,我个人觉得也没多高了,反正你跑不了
要用做偏移量的加法来寻址)

十一、为什么连续的内存有易于提高访问速点?

局部性原理
在这里插入图片描述

总结:

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

2023框框

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

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

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

打赏作者

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

抵扣说明:

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

余额充值