动态内存管理四大常用函数--malloc,calloc,realloc,free以及动态内存管理的常见问题

目录

1.malloc

2.free

3.calloc

4.realloc

在动态内存管理中的常见错误

练习:

C/C++中程序内存划分

柔性数组(了解)

动态内存管理的主要函数有malloc,calloc,realloc,free等,这些函数操作的数据都是在堆区上的,我们的内存分为了栈区,堆区,静态区,栈区主要用来存放局部变量和形式参数,堆区主要是用于动态内存开辟,静态区主要存放的是全局变量和static修饰的变量也即静态变量。

1.malloc

其头文件是stdlib.h,格式如下

用于动态开辟内存,malloc会向内存申请一块size字节大小的内存空间,并返回这块空间的地址,不过返回的地址是void*类型的,在使用的时候要根据对应的类型进行强制类型转换。

注:1.如果开辟成功,则返回一个指向开辟好空间的指针。如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。并可以使用perror("malloc")这一语句。

2.返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。

3.如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器。

4.malloc只是向内存堆区申请一块空间,并不会初始化,如果我们打印,结果就是这样

动态内存管理四大常用函数--malloc,calloc,realloc,free以及动态内存管理的常见问题_第1张图片

这些结果就是内存中的一些随机值

2.free

malloc申请的内存空间在程序推出之前是不会还给操作系统的,需要使用free函数释放

free函数的参数就是要释放的那块空间的起始地址,你可能有疑问,要释放空间,只给了这块空间的首地址,那到底释放多大空间呢?这是因为free只能释放动态开辟的空间,且一般与malloc等函数配合使用,如果与malloc配合使用,那么malloc申请了多大空间,free就释放多大空间。

注:1.如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。

2.如果参数 ptr 是NULL指针,则函数什么事都不做。

3.在使用完free函数之后应该把free置为空指针NULL

因此在动态内存管理中我们写的最多的代码就是free(p);p=NULL;

3.calloc

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

要开辟10个整形的空间,就这样写

4.realloc

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

ptr是要调整的内存的首地址,这个地址只能是由malloc,calloc,realloc申请的空间的首地址,如果传的ptr是NULL,那么realloc就不知道从哪里开始调整空间,他的作用就是随机申请一块空间,那么此时realloc等价于malloc

size是调整之后的新大小,比如我原来申请了四十个字节,我想要扩展到80个字节,那size的值就是80。

返回值是一个void*类型的地址,这个地址可能是ptr,也可能不是。这是因为在调整已经动态开辟的内存空间大小的时候,可能后面的空间足够,比如我申请了40个字节,我想扩展到80个字节,如果空间足够,直接开辟就完事了,然后返回ptr即可,当然也有可能在扩展的时候后面的空间已经被占用了,此时realloc就是再找一块新的内存空间,这块空间足以放80个字节,然后realloc会申请这块空间并把原来以ptr为首地址的那40个字节的空间中的内容拷贝到新开辟的空间中,然后把原来申请的那40个字节的空间释放掉。此时的返回值就是新开辟的空间的首地址。

如果realloc调整大小失败,会返回空指针NULL,也就是说realloc的返回值有三种情况,第一种是返回原来申请的那块空间的首地址,第二种是返回新开辟的空间的首地址,第三种是返回空指针,如果调整之前那块空间的地址使用一个指针变量p来存放的,那么使用realloc调整之后的地址,还能不能用这个地址存放?如果是前两种情况,当然可以使用p来存放,但是如果返回值是NULL,p就变成了空指针,以前p还维护了一块40个字节大小的空间,现在变成空指针了,就什么也不指向了,而且既然realloc调整失败了,那新的内存也肯定是没有开辟出来,这样就会导致一种情况就是:以前的那40个字节大小的空间,我们找不到了。这就是内存泄漏。为了避免内存泄漏的发生,我们可以这样写

动态内存管理四大常用函数--malloc,calloc,realloc,free以及动态内存管理的常见问题_第2张图片

在把realloc的返回值赋给某个指针的时候先判断一下他是不是NULL,如果不是,再赋值。

注:在使用完realloc函数之后,如果调整的空间不用了,也要及时用free函数释放掉

当然realloc也可以减少内存空间,如果是减少内存空间,就非常简单了,返回值一定是原来在堆区申请的那块空间的首地址。

在动态内存管理中的常见错误

第一,我们在使用malloc,calloc,realloc在堆区开辟空间的时候,有可能会开辟失败而返回一个空指针NULL,对他进行解引用是非法的,因此我们在动态内存管理中使用返回的地址之前应该先判断是否为NULL

第二,对动态开辟空间的越界访问。如果我们用malloc开辟了40个字节空间,但是却想访问第41个空间,就会发生错误

第三,对非动态开辟内存使用free释放,程序会直接崩溃报错

第四, 使用free释放一块动态开辟内存的一部分,举个例子

动态内存管理四大常用函数--malloc,calloc,realloc,free以及动态内存管理的常见问题_第3张图片

这里在for循环里面p已经被改变了,后面free(p),就只会释放掉部分开辟的内存,程序会崩溃报错。

第五,对同一块内存多次释放或者忘记释放。如果忘记释放,会造成内存泄漏。比如这样一段代码

动态内存管理四大常用函数--malloc,calloc,realloc,free以及动态内存管理的常见问题_第4张图片

调用test函数的时候用malloc开辟了一块空间,并用p维护这块空间,但是出了这个函数,p就销毁了,就会导致无法找到这块空间了,这就是内存泄漏,那在调用完test之后的while循环里就再也无法使用这一百个字节的空间了。

练习:

动态内存管理四大常用函数--malloc,calloc,realloc,free以及动态内存管理的常见问题_第5张图片

在给GetMemory传参的时候,看起来GetMemory是接受了一个地址,这是否是传址调用呢?当然不是,因为我们传的str本身就是一个指针,然后只是把str的值传给了GetMemory,因此这里采用的传参方式是传值调用,形参是实参的一份临时拷贝,对形参的操作并不会改变实参,因此str还是NULL,后面要把hell world拷贝到NULL指向的空间去,显然不正确,因此这个程序会因为对空指针进行解引用而崩溃,而且这里在GetMemory函数中申请了空间并没有释放掉,出了函数之后p销毁,导致这块内存找不到了,会造成内存泄漏。

这个print的使用其实是正确的,假如我们要打印hello world,写printf("hello world")肯定是正确的吧,那这个"hello world"实际上给printf的就是h的地址,现在我们把h的地址放在一个指针变量str里面,直接printf(str)实际上是一样的。

C/C++中程序内存划分

动态内存管理四大常用函数--malloc,calloc,realloc,free以及动态内存管理的常见问题_第6张图片

全局变量和静态变量放在数据段,局部变量放在栈区,常量字符串放在代码段,malloc,calloc,realloc申请的空间放在堆区。

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

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

2. 堆区(heap):一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。分配方式类似于链表。

3. 数据段(静态区)(static)存放全局变量、静态数据。程序结束后由系统释放。4. 代码段:存放函数体(类成员函数和全局函数)的二进制代码。

这也就是为什么使用static修饰局部变量之后会使得局部变量的生命周期变长,因为原来的局部变量在栈区上,函数调用结束之后空间被自动释放,被static修饰之后就到静态区上存放了,在程序结束时候由系统自动释放。

柔性数组(了解)

在结构体类型的声明中,如果这个结构体类型的最后一个成员变量是一个数组,且这个成员变量前面至少还有一个成员变量,那么最后面这个数组大小就可以是未知的,如果我们定义成这样

动态内存管理四大常用函数--malloc,calloc,realloc,free以及动态内存管理的常见问题_第7张图片

那么这个数组a就是一个柔型数组,如果编译器报错,可以写成

动态内存管理四大常用函数--malloc,calloc,realloc,free以及动态内存管理的常见问题_第8张图片

柔型数组的特点

结构中的柔性数组成员前面必须至少一个其他成员。

sizeof 返回的这种结构大小不包括柔性数组的内存。

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

实际上要想最后一个元素的大小可变,我们完全可以使用一个指针,并不需要使用柔性数组,因此柔型数组的使用场景并不多。

你可能感兴趣的:(c语言,开发语言,数据结构,c++,算法)