C语言——动态内存管理

目录

1. 为什么存在动态内存分配

2. 动态内存函数的介绍

        2.1 malloc和free

        2.2  calloc

        2.3 realloc

3. 常见的动态内存错误

        3.1 对NULL指针的解引用操作

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

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

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

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

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

4. 经典例题

5. 柔性数组

        5.1 柔性数组的特点

        5.2 柔性数组的使用

        5.3 柔性数组的优势

结语


1. 为什么存在动态内存分配

我们已经知道的内存开辟方式有:

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

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

但是上述的开辟空间的方式有两个特点:
1. 空间开辟大小是固定的。
2. 数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配。
但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候才能知道,那数组的编译时开辟空间的方式就不能满足了,这时候就只能试试动态存开辟了。

2. 动态内存函数的介绍

        2.1 malloc和free

mallocfree都声明在 <stdlib.h> 头文件

void* malloc (size_t size);

这个函数向内存申请一块 连续可用 的空间,并返回指向这块空间的指针。
如果开辟成功,则返回一个指向开辟好空间的指针,也就是起始地址。
如果开辟失败,则返回一个 NULL 指针,因此 malloc 的返回值一定要做检查。
返回值的类型是 void* ,所以 malloc 函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。
如果参数 size 0 malloc 的行为是标准是未定义的,取决于编译器。
C语言——动态内存管理_第1张图片 可以看到旁边的内存窗口p的内容已经被修改了。
C 语言提供了另外一个函数 free ,专门是用来做动态内存的释放和回收的
free 函数用来释放动态开辟的内存。
如果参数 ptr 指向的空间不是动态开辟的,那 free 函数的行为是未定义的。
如果参数 ptr NULL 指针,则函数什么事都不做。
C语言——动态内存管理_第2张图片
上面就是我们开辟好并且赋值的内存空间,使用free就可以释放这些空间

C语言——动态内存管理_第3张图片

free传入的是开辟的内存空间的起始地址,之后判断一下ptr是不是NULL,再去写下面的代码

C语言——动态内存管理_第4张图片

当然malloc申请的空间不能太大,INT_MAX是整型最大值,想要申请那么大的空间是不可能的,malloc申请失败就会返回空指针,perror就会帮我们打印出:没有足够的空间。

C语言——动态内存管理_第5张图片

        2.2  calloc

C 语言还提供了一个函数叫 calloc calloc 函数也用来动态内存分配。
原型如下:
void* calloc ( size_t num , size_t size );
函数的功能是为 num 个大小为 size 的元素开辟一块空间,并且把空间的每个字节初始化为 0
与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全 0
C语言——动态内存管理_第6张图片

C语言——动态内存管理_第7张图片

        2.3 realloc

realloc 函数的出现让动态内存管理更加灵活。
有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的时候内存,我们一定会对内存的大小做灵活的调整。那 realloc 函数就可以做到对动态开辟内存大小的调整。
void* realloc ( void* ptr , size_t size );
ptr 是要调整的内存地址
size 调整之后新大小
返回值为调整之后的内存起始位置。
这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到 的空间。
realloc 在调整内存空间的是存在两种情况:
情况 1 :原有空间之后有足够大的空间
C语言——动态内存管理_第8张图片
情况 2 :原有空间之后没有足够大的空间
C语言——动态内存管理_第9张图片
       原有空间之后没有足够多的空间时,扩展的方法是:在堆空间上另找一个合适大小的连续空间来使用。这样函数返回的是一个新的内存地址。

       当我们在使用realloc函数的时候,最好先把重新申请的空间地址放在一个临时变量中,如果没有申请成功就会返回一个空指针,所以申请成功了再把该地址赋值到开始的地址,如同下面的ptr就是一个临时变量,p才是我们要放置的地址。

int main()
{
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		*(p + i) = i;;
	}
	int* ptr = (int*)realloc(p, 80);
	if (ptr != NULL)
	{
		p = ptr;
	}
	//扩容成功,可以使用

	free(p);
	p = NULL;

	return 0;
}

3. 常见的动态内存错误

在我们使用内存操作函数时,一定要注意一写常见的错误,即使是一个小错误,也不能忽视

        3.1 NULL指针的解引用操作

        首先就是当我们向内存申请空间时,可能会申请失败,一定要判断返回的指针是不是空指针,如果是就会造成对空指针解引用的问题,这时就要及时返回,不要再运行后面的代码了。
C语言——动态内存管理_第10张图片

C语言——动态内存管理_第11张图片

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

申请了100个字节可以放25个整型,但下面放了26个,会造成越界访问

 C语言——动态内存管理_第12张图片

 我们在运行程序的时候,就会出现问题,所以在操作的时候要检查内存边界。

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

C语言——动态内存管理_第13张图片

我们自己创建的局部变量是不可以用free来释放的,它会在程序结束时被自动回收,而我们向内存申请的动态内存必须要free来释放。

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

申请了100个字节的空间,25个整型,这里访问了10个后。p指向了第11个位置,这是free释放了从第11开始的位置,而前10个没有被释放,在运行时就会出现错误。
C语言——动态内存管理_第14张图片

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

C语言——动态内存管理_第15张图片

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

C语言——动态内存管理_第16张图片

这里就如同我们模拟一个一直在运行的程序,如果一直申请而不去释放内存,内存迟早就会满了。

4. 经典例题

(1)

C语言——动态内存管理_第17张图片

可以看到这里什么都没有打印,其实是崩溃了。

C语言——动态内存管理_第18张图片

       看一下上面这个提示,str可能是0,也就是空指针,当我们调用这个GetMemory函数时,创建了p变量来申请内存,但是出了函数后,申请的内存的位置没有变量记住,也没有返回,所以str还是空指针,strcpy函数对空指针进行解引用就会出现问题,就会非法访问内存,而且申请的内存空间也没有释放,会造成内存泄漏。

C语言——动态内存管理_第19张图片

       对上面的代码进行修改,使得最后打印出我们想要的内容,第一个问题就是我们申请的内存没有记住,那直接把str的地址传进去,就可以直接改变str的内容,str是一个一级指针,那函数就直接用二级指针来接收,再解引用操作对str申请内存,这样就可以把申请的100个字节放到str中,最后再free(str)。

(2)

C语言——动态内存管理_第20张图片

       上面没有申请空间,所以不存在没有free的情况,但打印的还是乱码 ,这是因为GetMemory函数中p是字符数组,记录了'h'的位置,当函数调用完这块空间就销毁了,被操作系统回收了,但还是记住了这块空间的地址,但里面的内容已经被修改了,并不知道被改成了什么,当我们想要访问str的时候,此时str就是个野指针。这就是返回了栈空间的地址的问题,非常容易造成野指针的问题。

(3)

C语言——动态内存管理_第21张图片

       这里的问题就相当的简单了,malloc申请了空间,但是没有free,想要改正确就free一下并且赋值成NULL就可以了。

(4)

C语言——动态内存管理_第22张图片

       虽然这里打印了world,但是这个代码还是有问题的,编译器也已经报了警告,当我们申请了空间,并且strcpy复制了之后,str这个地址就已经被free了,但并没有赋值为NULL,所以这里面还是放置了内容的,并不是空指针,如果再被访问了就会造成非法访问的问题,所以当我们使用完之后,free之后一定要赋值成NULL。

最后声明一下,这些题目都出自高质量C++/C编程这本书最后的编程题。


5. 柔性数组

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

        5.1 柔性数组的特点

(1)结构中的柔性数组成员前面必须至少一个其他成员。
(2)sizeof 返回的这种结构大小不包括柔性数组的内存。
C语言——动态内存管理_第23张图片

(3)包含柔性数组成员的结构用 malloc () 函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。
C语言——动态内存管理_第24张图片 

        5.2 柔性数组的使用

C语言——动态内存管理_第25张图片

C语言——动态内存管理_第26张图片

        5.3 柔性数组的优势

方便释放内存

C语言——动态内存管理_第27张图片

像这样使用指针其实也可以达到相同的效果 

C语言——动态内存管理_第28张图片

C语言——动态内存管理_第29张图片

 C语言——动态内存管理_第30张图片

利于访问速度

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

C语言——动态内存管理_第31张图片

结语

       这一篇关于动态内存操作的相关知识就介绍到这里,继续下一篇。

你可能感兴趣的:(c语言)