动态内存管理

目录

一:为什么存在动态内存分配?

二、动态内存函数的介绍

malloc

free 

 calloc

 realloc

  三、常见的动态内存错误

四、经典例题

五、C/C++程序的内存开辟

六、柔性数组


一:为什么存在动态内存分配?

前面已经常用的两种内存开辟方式是:

int val=20;//在栈空间上开辟四个字节

char arr[10]={0};//在栈空间上开辟10个字节的连续空间

这两种方式的特点:

        1、空间开辟的大小是固定的。

        2、数组在申明的时候,必须指定数组的长度,它所需要的内存在编译 时开辟。

问题是:这样太死板,比如有时候需要的空间大小在程序运行的时候才能知道,那数组的编译时开辟的空间也许不能满足,这时就只能动态开辟了。

动态内存管理:

静态往往和“编译时”相关,而动态往往和“运行时”相关,动态是指程序运行过程中,更灵活的进行申请和释放。

二、动态内存函数的介绍

malloc

动态内存开辟的函数:

void * malloc (size_t  size)     

  • 头文件:#include<stdio.h>
  • 参数是无符号整型,即要申请的字节数,且是一个连续的内存空间。
  • 返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。
  • 如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器。
  • 如果开辟成功,则返回一个指向开辟好空间的指针。
  • 如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查(监控报警)。
  • 监控报警:就针对内存而言,当内存使用超过50%的时候就报警,以防万一有紧急情况出现。

free 

释放动态开辟的内存:

void free (void* ptr)

  • 头文件:#include<stdio.h>
  • 如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。
  • 如果参数 ptr 是NULL指针,则函数什么事都不做。

malloc函数的使用: 

int main()
{
	int *ptr=(int*)malloc(10 * sizeof(int));//分配40个字节大小的内存空间,在堆上
	//int* ptr = (int*)malloc(10 * 1024*1024*1024);//分配失败,最多分配2G左右大小
	if (ptr == NULL)
	{
		printf("分配失败\n");
	}
	assert(ptr);
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", ptr[i]);//随机值
	}
	free(ptr);//程序员手动释放这段空间
	if (ptr != NULL)
	{
		printf("ptr!=NULL");//打印出来了,说明只是释放了ptr所指向的这一段内存,但是ptr还是指向这个首地址,没有NULL,要手动赋值NULL,不然ptr就是野指针
		ptr = NULL;
	}
	
	return 0;
}

 calloc

也用来动态内存分配:

void* calloc (size_t num, size_t size)

  • 函数的功能是为num个大小为size的元素开辟一块内存空间,并把空间的每个字节初始化为0,所以如果我们对申请的内存空间的内容要求初始化,那么可以很方便的使用calloc函数来完成。
  • 与函数malloc的区别只在于calloc会在返回地址之前把申请的空间的每个字节初始化全为0.

calloc函数的使用: 

int main()
{
	int* ptr = (int*)calloc(10 , sizeof(int));//分配40个字节大小的内存空间,在堆上
	//int* ptr = (int*)malloc(10 * 1024*1024*1024);//分配失败,最多分配2G左右大小
	if (ptr == NULL)
	{
		printf("分配失败\n");
	}
	assert(ptr);
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", ptr[i]);//初始值都为0
	}
	free(ptr);//程序员手动释放这段空间
	if (ptr != NULL)
	{
		printf("ptr!=NULL");//打印出来了,说明只是释放了ptr所指向的这一段内存,但是ptr还是指向这个首地址,没有NULL,要手动赋值NULL,不然ptr就是野指针
		ptr = NULL;
	}
	return 0;
}

 realloc

  • realloc函数的出现让动态内存管理更加灵活。
  • 有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的时候内存,因此用realloc函数对动态开辟内存大小的调整(扩容):

 void* realloc (void* ptr, size_t size)

  • ptr 是要调整的内存地址
  • size 调整之后新大小
  • 返回值为调整之后的内存起始位置
  • 这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到 新 的空间。
  • realloc进行扩容的两种情况:
    • 1、原有空间之后有足够大的空间,就直接在原内存空间后面直接追加,原来空间的数据不发生变化。
    • 2、原有空间之后没有足够大的空间,此时就需要在堆空间上另外找一个更大的连续的空间,把原来内存的数据拷贝过去,返回新的空间的地址,并释放旧的空间。

 void * ptr2= realloc(ptr,20)

此处的ptr和ptr2可能相等,也可能不相等

int main()
{
	int* ptr = (int*)malloc(10 * sizeof(int));//分配40个字节大小的内存空间,在堆上
	assert(ptr);
	for (int i = 0; i < 10; i++)
	{
		*(ptr + i) = i;
		printf("%d ", *(ptr + i));
	}
	printf("\n");
	int *ptr2=(int*)realloc(ptr, 10 * sizeof(int) * 2);//要括存的地址空间,括存为原来的几倍。
	assert(ptr2);
	if (ptr2 != NULL)//万一没有扩容成功,反而会使得ptr为NULL,找不到原来的值,因此要判断扩容是否成功,如果成功则将ptr2赋值给ptr
	{
        free(ptr);
		ptr = ptr2;
	}
	for (int i = 0; i < 20; i++)
	{
		printf("%d ", *(ptr + i));//扩充后的内容是随机值,并且会把原来的值复制过来
	}
	return 0;
}

  三、常见的动态内存错误

  • 对NULL指针的解引用操作:
void test()
{
 int *p = (int *)malloc(INT_MAX/4);
 *p = 20;//如果p的值是NULL,就会有问题
 free(p);
}
  •  对动态开辟空间的越界访问:
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;//当i是10的时候越界访问
 }
 free(p);
}
  •  对非动态开辟内存使用free释放:
void test()
{
 int a = 10;
 int *p = &a;
 free(p);//ok?  wrong
}
  •  使用free释放一块动态开辟内存的一部分:
void test()
{
 int *p = (int *)malloc(100);
 p++;
 free(p);//p不再指向动态内存的起始位置
}
  •  对同一块动态内存多次释放:
void test()
{
 int *p = (int *)malloc(100);
 free(p);
 free(p);//重复释放
}
  •  动态开辟内存忘记释放(内存泄漏):
void test()
{
 int *p = (int *)malloc(100);
 if(NULL != p)
 {
 *p = 20;
 }
}
int main()
{
 test();
 while(1);
}

 忘记释放不再使用的动态开辟的空间会造成内存泄漏。 切记: 动态开辟的空间一定要释放,并且正确释放 。

四、经典例题

题目1:请问运行Test 函数会有什么样的结果?答:不能打印。

char* GetMemory(char* p)
{
	p = (char*)malloc(100);
	char* pp = p;
	free(p);
	p = NULL;
	return pp;//改进之后则可以打印,但是本质上已经释放了p所指向的内存,电脑已经不保证那段内存,只是pp还保存了那段内存的地址  
}
void Test(void)
{
	char* str = NULL;
	str=GetMemory(str);
	strcpy(str, "hello world");//此时str是空指针,没有申请的内存空间,如果str是数组则可以拷贝
	printf(str);//等同于printf("%s",str);
}

错误: 

  • 没有free
  • 没有判定malloc的返回值是否为NULL
  • 当前GetMemory函数并没有修改实参str的值,str仍然是NULL,后面strcpy操作就会出现越界访问的情况

题目2:

char* GetMemory(void)
{
	char p[] = "hello world";
	return p;//局部变量,当前代码块就是生命周期,当当前的函数执行结束,这个内存就释放了
}
void Test(void)
{
	char* str = NULL;
	str = GetMemory();
	printf(str);
}

分析

 p是局部变量,退出GetMemory这个函数之后p这个数组占用的栈被回收,但p这个地址依然存在并且被返回,但里面存的内容是随机的,因此打印出来是随机值。

题目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);
}

分析

可以成功打印。

str是一级指针,&str是二级指针,p是二级指针,*p是一级指针,在GetMemory函数中修改一级指针*p即str指针的值,给其开辟空间并返回初始地址值,故可以拷贝成功。

注意:形参怎么写,关键看实参是什么类型

错误

  • malloc返回结果没有检查
  • 没有free

题目4:

void Test(void)
{
	char* str = (char*)malloc(100);
	strcpy(str, "hello");
	free(str);//没有置str=NULL,str是野指针,不为空
	if (str != NULL)
	{
		strcpy(str, "world");//赋值成功,但有可能会篡改,访问是非法的
		printf(str);
	}
}

 分析:

free只是释放了str所指向的内存空间,str没有变,但str是野指针。

五、C/C++程序的内存开辟

C/C++程序内存分配的几个区域:

1、栈区(stack):空间比较小,申请释放速度非常快。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些 存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。

2、 堆区(heap):空间比较大,申请释放的速度比较慢。一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。分配方式类似 于链表。

3. 数据段(静态区)(static)存放全局变量、静态数据。程序结束后由系统释放。

4. 代码段:存放函数体(类成员函数和全局函数)的二进制代码。

 实际上普通的局部变量是在栈区分配空间的,栈区的特点是在上面创建的变量出了作用域就销毁。 但是被static修饰的变量存放在数据段(静态区),数据段的特点是在上面创建的变量,直到程序结束才销毁 所以生命周期变长。

5.C++开辟内存的方式&C开辟内存方式的对比

void main()
{
	int* p1 = (int*)malloc(sizeof(int));
	if (p1 == NULL)
		return;
	free(p1);

	int* p2 = new int(1);
	delete p2;
}

从上述代码段可以发现:

①malloc需要说明大小,new不用

②malloc需要强转,new不用

③malloc需要判断返回值是否为空,new不用

④malloc不能初始化,new可以随机初始化

六、柔性数组

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

例如:

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

有些编译器会报错无法编译可以改成:

typedef struct st_type
{
 int i;
 int a[];//柔性数组成员
}type_a;

 柔型数组的特点:

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

例如:

typedef struct st_type
{
 int i;
 int a[0];//柔性数组成员
}type_a;
printf("%d\n", sizeof(type_a));//输出的是4
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值