【C语言进阶(11)】动态内存管理

文章目录

  • Ⅰ 存在动态内存分配的原因
  • Ⅱ 动态内存函数
    • 1. malloc
    • 2. calloc
    • 3. realloc
    • 4. free (重要)
  • Ⅲ 常见动态内存错误
    • 1. 对 NULL 指针的解引用操作
    • 2. 对动态开辟空间的越界访问
    • 3. 对非动态开辟内存使用 free 释放
    • 4. 使用 free 释放一块动态开辟内存的一部分
    • 5. 对同一块动态内存多次释放
    • 6. 动态开辟内存没有释放(内存泄漏)
  • Ⅳ 柔性数组

引用头文件

stdlib.h

Ⅰ 存在动态内存分配的原因

内存使用的方式

  1. 创建变量(开辟一块独立的空间)

    • 局部变量 -> 栈区。
    • 全局变量 -> 静态区。
  2. 创建数组(开辟一块连续的 空间)

    • 局部数组 -> 栈区。
    • 全局数组 -> 静态区。

上述开辟空间的方式有两个特点

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

存在的问题

  • 不能灵活的开辟空间。如果需要的空间很少,但是固定开辟的空间非常大,就会造成内存浪费。如果需要的空间很多,固定开辟的空间很小,就会造成内存不够用的情况。
  • 因此就出现了动态内存函数,用来随时更改所开辟的空间的大小。

Ⅱ 动态内存函数

1. malloc

函数原型

void* malloc (size_t size);

函数功能

  • 开辟一块 size 个字节大小连续可用的空间,并返回该空间的起始地址。

函数返回值

  • 如果开辟成功,则返回一个指向开辟好空间的起始地址的指针。
  • 如果开辟失败,则返回一个 NULL 指针。因此要检查好 malloc 的返回值。
  • 返回值的类型是 void*,在使用指向开辟好的空间的起始地址的指针时,还需要转换成自己需要的指针类型。

函数用例

  • 申请一块空间,用来存放 10 个整形。
#include 

int main()
{
	int* p = (int*)malloc(10 * sizeof(int));
	//malloc 返回的是 void* 的指针,需要转换成对应的指针类型才能使用

	return 0;
}

2. calloc

函数原型

void* calloc (size_t num, size_t size);

函数功能

  • 为 num 个大小为 size 的元素开辟一块空间,并且把该空间的每个字节初始化为 0。
  • 与 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0。

函数用例

  • 开辟一块具有 10 个元素,每个元素 4 个字节的空间。

【C语言进阶(11)】动态内存管理_第1张图片

3. realloc

函数原型

void* realloc (void* ptr, size_t size);

函数功能

  • 调整由 ptr 所指向的空间大小为 size 个字节。

函数参数

  • ptr:指向由 malloc、calloc 所开辟的空间。
  • size:新空间的大小(字节)。

realloc 函数的两种使用情况

void* realloc (void* ptr, size_t size);
  1. 将 ptr 所指向的空间大小重新调整为 size 个字节。
void* realloc(NULL,size_t size);
  1. 开辟一块有 size 个字节的空间。

函数用例

在这里插入图片描述

realloc 调整空间的方式

realloc在调整内存空间时存在两种情况

  1. 情况 1:原有空间之后足够大的空间,扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发生变化,返回旧地址

【C语言进阶(11)】动态内存管理_第2张图片

  1. 情况 2:原有空间之后没有足够大的空间,realloc 会重新找一个内存区域开辟一块符合要求的空间,返回新地址,拷贝旧数据,释放旧空间

【C语言进阶(11)】动态内存管理_第3张图片

4. free (重要)

函数原型

void free (void* ptr);

函数参数

  • ptr:指向先前用 malloc、calloc、realloc 分配的内存块的起始地址。

函数功能

  • 释放由 ptr 指向的动态开辟的空间。

函数用例

  • 释放由各个动态内存函数所开辟的空间。
int main()
{
	int* p1 = (int*)malloc(10 * sizeof(int));
	int* p2 = (int*)calloc(10, sizeof(int));
	int* p3 = (int*)realloc(NULL, sizeof(int) * 10);
	
	//......
	
	free(p1);
	free(p2);
	free(p3);

	return 0;
}

注意事项

  • free 会将指针指向的空间释放掉,但是不会改变指针内存放的地址值。
  • 也就是说,即使 free 掉了 p1 p2 p3 三个指针所指向的空间,这三个指针依然记得原先空间的起始地址。
  • 因此释放掉指针所指向的空间之后,还必须要将该指针变量的值置为 NULL。
int main()
{
	int* ptr = (int*)malloc(10 * sizeof(int));

	//......

	free(ptr);	//释放掉ptr指向的空间后,ptr内存的地址值未变,还是记得原来空间的地址
	ptr = NULL;	//因此要主动修改ptr内存放的地址值为NULL
	
	return 0;
}

Ⅲ 常见动态内存错误

1. 对 NULL 指针的解引用操作

  • 在使用之前未判断动态内存函数的返回值是否是 NULL,如果内存开辟失败返回的是 NULL,就成了对 NULL 指针的解引用操作。
int* p = (int*)malloc(40);
*p = 20;//使用前为判断指针 p 内的值是否有效

正确做法

int* p = (int*)malloc(40);

if (NULL == p)	//使用之前先判断该空间是否开辟成功
{
	return 1;	//1 为异常返回
}

*p = 20;

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

  • 动态申请的空间也有着自己的范围,不能无限制的使用。
int* p = calloc(10, sizeof(int));

if (NULL == p)
{
	perror("calloc");
	return 1;
}
	
for (int i = 0; i <= 10; i++)//当 i 到 10 时越界访问
{
	printf("%d ", *(p + i));
}
free(p);
p = NULL;

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

  • free 只能释放由 malloc、calloc、realloc 开辟的空间。

【C语言进阶(11)】动态内存管理_第4张图片

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

  • 指向开辟空间起始地址的指针产生变动,没有从动态开辟内存的起始地址开始释放空间。

【C语言进阶(11)】动态内存管理_第5张图片

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

  • 忘记了已经释放过动态开辟的空间,又重新释放了一遍。
int* p = (int*)malloc(10 * sizeof(int));
//......
free(p);//此时已经释放过该空间一次
//.....
free(p);//睡蒙了又释放一次

解决办法

  • 在释放完动态开辟好的内存后,及时将该指针置为 NULL。之后如果再重复释放也不会产生问题。

6. 动态开辟内存没有释放(内存泄漏)

  • 只记得申请空间,不记得还。
void test()
{
	int* p = (int*)malloc(10 * sizeof(int));
	//p 是局部变量,出了作用域就销毁,等这个函数一结束就没人再记得这块空间的起始地址了

	if (true)//此时动态开辟的空间永远没机会释放了
	{
		return;
	}

	free(p);
	p = NULL;
}

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

Ⅳ 柔性数组

  • C99 中,结构中的最后一个元素允许是未知大小数组,这就叫做『柔性数组』成员。
struct S
{
	char c;
	int i;
	int arr[];	//未知大小数组 - 柔性数组成员
};

柔性数组的特点

  • 结构体中的柔性数组成员前面必须至少有一个其他成员。
  • sizeof 返回的这种结构体大小不包含柔性数组成员的大小。
  • 包含柔性数组成员的结构体用 malloc 函数进行内存的动态分配,并且分配的内存应该大于结构体的大小,以适应柔性数组的预期大小。
struct S
{
	char c;
	int i;
	int arr[];
};

int main()
{
	struct S* ps = (struct S*)malloc(sizeof(struct S) + 20);
	//这 20 个字节才是分配给柔性数组 arr 的

	return 0;
}

你可能感兴趣的:(#,C语言进阶篇,c语言,数据结构)