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释放一块动态开辟内存的一部分

扫描二维码关注公众号,回复: 14176011 查看本文章

        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 的行为是标准是未定义的,取决于编译器。
 可以看到旁边的内存窗口p的内容已经被修改了。
C 语言提供了另外一个函数 free ,专门是用来做动态内存的释放和回收的
free 函数用来释放动态开辟的内存。
如果参数 ptr 指向的空间不是动态开辟的,那 free 函数的行为是未定义的。
如果参数 ptr NULL 指针,则函数什么事都不做。
上面就是我们开辟好并且赋值的内存空间,使用free就可以释放这些空间

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

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

        2.2  calloc

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

        2.3 realloc

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

       当我们在使用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指针的解引用操作

        首先就是当我们向内存申请空间时,可能会申请失败,一定要判断返回的指针是不是空指针,如果是就会造成对空指针解引用的问题,这时就要及时返回,不要再运行后面的代码了。

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

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

 

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

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

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

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

申请了100个字节的空间,25个整型,这里访问了10个后。p指向了第11个位置,这是free释放了从第11开始的位置,而前10个没有被释放,在运行时就会出现错误。

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

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

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

4. 经典例题

(1)

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

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

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

(2)

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

(3)

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

(4)

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

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


5. 柔性数组

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

        5.1 柔性数组的特点

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

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

        5.2 柔性数组的使用

        5.3 柔性数组的优势

方便释放内存

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

 

利于访问速度

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

结语

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

猜你喜欢

转载自blog.csdn.net/m0_64607843/article/details/123802893