前言:
通常,我们在栈空间开辟的内存都是固定的,这是十分不方便使用的。为了更加灵活的分配和使用内存,我们要学习C语言中一些常用的与内存分配相关联的函数。顺便,我们会补充数组中柔性数组的知识。
内存分区模型:
本期内容,就是学习动态内存分配,着手堆区的使用。下面进入正文部分。
动态内存函数
包含头文件 <stdlib.h>
🪂1、malloc
作用:这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针。
🛸🛸格式:malloc(size_t size);
🎄🎄如果开辟成功,则返回一个指向开辟好空间的指针。🎄🎄如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。🎄🎄返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。(用强制类型转换转换成需要的类型即可)🎄🎄如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器。使用:
#include <stdio.h> #include <stdlib.h> int main() { int* p = (int*)malloc(40); if (p == NULL) { perror("malloc"); } else { for (int i = 0; i < 10; i++) { *(p + i) = i; } for (int j = 0; j < 10; j++) { printf("%d ", *(p + j)); } } free(p); p = NULL; return 0; }
输出结果:0 1 2 3 4 5 6 7 8 9
🪂2、free
C 语言提供了另外一个函数 free ,专门是用来做动态内存的释放和回收的,函数原型如下:🛸🛸 格式:free (void* ptr) ;🎄🎄如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。🎄🎄如果参数 ptr 是NULL指针,则函数什么事都不做。注意: 在使用malloc之后一定要记得使用free。否则,将一直占用这块内存,造成不必要的浪费,并且也会容易造成内存泄漏。在free完之后,也要记得将指向malloc开辟的空间的指针置空。
🪂3、calloc
C语言中还提供了一个calloc函数来动态内存分配
格式:calloc( size_t num, size_t size );
🎄🎄这个函数与malloc功能很相似,唯一的区别在于它在返回地址之前会把申请的空间每个字节初始化为0。
🪂4、realloc
作用:对已经开辟的内存的大小进行调整
格式:realloc( void *memblock, size_t size );
🎄🎄realloc函数的出现使得动态内存管理更加灵活。
🎄🎄memblock是要调整的内存地址,通常这是一个指向malloc开辟的内存的指针。
🎄🎄size是调整之后的新大小,而不是要增加的大小。
🎄🎄返回值是调整之后的内存起始位置。
🎄🎄这个函数调整原内存空间大小的基础上,还会原来的内存中的数据移动到新的空间。
realloc在调整内存空间时存在两种情况:
1、空间足够的话,在原来的空间尾部开辟新的空间
2、在另一个位置开辟一个足够大小的内存空间,并且将原来空间中的数据全部拷贝过去
使用:
#include <stdio.h> #include <stdlib.h> int main() { int* p = (int*)malloc(40); if (p == NULL) { perror("malloc"); } else { for (int i = 0; i < 10; i++) { *(p + i) = i; } for (int j = 0; j < 10; j++) { printf("%d ", *(p + j)); } printf("\n"); } int* p2 = (int*)realloc(p,60); if (p2 == NULL) { perror("realloc"); } else { p = p2; p2 = NULL; } for (int i = 0; i < 15; i++) { *(p + i) = i; printf("%d ", *(p + i)); } free(p); p = NULL; return 0; }
结果:1 2 3 4 5 6 7 8 9 10 11 12 13 14
常见的动态内存开辟错误
🪂1、对NULL指针的解引用操作
🪂2、对动态开辟空间越界访问
这里当i=10的时候访问,就越界了。
这样做还容易卡死。
🪂3、对非动态开辟的内存使用free释放
4、使用free释放动态内存开辟的一部分
这里把p改变了然后释放,这样的做法是错误的。
🪂5、多次用free释放同一块空间
这里就就体现将指针置空的重要性了。
如果使用完将指针置空,再次free释放的时候编译器什么事情都不会干。
🪂6、动态内存开辟忘记释放(free)
这样会造成内存泄漏。
几个经典的笔试题
🪂🪂1.
这里是把字符串放在字符数组中,出了函数p就销毁了,而str是指向p首元素的指针。但是p销毁了,str就变成野指针了。
🪂🪂2.
这里使用完没有用free释放并将指针置空。造成内存泄漏。
🪂🪂3.
使用malloc之后就应该判断是否为NULL。
并且free再次使用了这块空间,造成了越界访问。
柔性数组
结构体中最后一元素允许是未知大小的数组。
通常格式:类型 数组名[ ] 或者 类型 数组名[0]
🎄🎄结构中的柔性数组成员前面必须至少一个其他成员。
🎄🎄sizeof 返回的这种结构大小不包括柔性数组的内存。🎄🎄包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。
arr就是一柔性数组。
一般是用malloc来开辟空间大小来存储含有柔性数组的结构,并且大小要大于结构体的大小,以适应柔性数组的大小。
开辟的空间除了其他元素以外剩下的空间就是留给柔性数组的。
第二种方式开辟随意大小空间的数组,这种的方法要用两次malloc,两次free。可以看出,这种方法明显没有第一种柔性数组的好用。
两种方法的区别:
第一种方法的好处:
1、内存方便释放,只用释放一次。因为他们开辟的空载是在一个地方。
2、有利于访问速度,还是因为他们开辟的空间是在一个地方。
本文就到此结束了,再见!