目录
前言
我们知道内存以前的开辟方式有两个特点:
1.我们开辟的内存空间是固定的。
2.数组开辟必须指定数组的长度。
这样就使得我们如果申请内存空间太多导致浪费,申请内存空间太小导致数据放不下,因此就有了动态内存开辟。
1.动态内存函数介绍
(1)malloc 和 free
malloc
原型:void* malloc (size_t size);
1.malloc这个函数函数向内存申请一块连续的空间,申请成功返回指向这个空间的指针。申请失败返回NULL(空指针)。因此在使用前需要检查这个指针是否符合。
2.根据指针函数的原型可以看到返回类型是 void* ,因为这个函数在开辟空间时时不知道它开辟的类型,这个是使用者规定的。
3.如果我们向内存申请0个空间,这是malloc函数本身没有定义的,这个取决于我们的编译
器。
补充:malloc如果多次使用,可能会造成内存的碎片化
主要代码: int* p = (int*)malloc(40); //40可变,单位是字节 if (p == NULL) { printf("%s", strerror(errno)); //返回错误信息 return 1; }
free
原型:void free (void* ptr);
free函数是专门用来做动态内存释放和回收的,使用这个函数有下面几个前提。
1.如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。
2.如果参数 ptr 是NULL指针,则函数什么事都不做。
int* p = (int*)malloc(40); if (p == NULL) { printf("%s", strerror(errno)); return 1; } free(ptr); //释放malloc申请的空间 ptr = NULL; //此时ptr指向的地址是无效的空间,因此要赋值为NULL。防止野指针
(2)calloc
原型:void* calloc (size_t num, size_t size);
1.calloc其中num个大小为size的空间,并且把空间的每一个字节初始化为0。
2.与函数malloc的区别在于,calloc在返回时会把申请的空间都初始化为0。
int main() { int* p = (int*)calloc(4, sizeof(int)); if (p == NULL) { printf("%s", strerror(errno)); return 1; } free(p); p = NULL; return 0; }
(3)realloc
原型:void* realloc (void* ptr, size_t size);
realloc这个函数使用起来相对于更加灵活,因为它可以改变我们已经申请的空间的大小。
1. ptr是要调整的地址
2. size是调整后的重新大小
3. 返回调整之后的起始位置
4. realloc在调整会有两种情况
①原来的空间后面足够我们增容
这种情况增容会在原有空间后面直接扩容
②原来的空间后面没有足够的空间增容
这种情况,realooc会在堆区重新找到一个合适的空间,并且自动将原有空间的数据拷贝到新的空间上。
三个函数的总结:
无论是malloc,calloc,realloc它们的作用都是动态开辟空间,只是三者开辟的需求不一样,malloc 和 realloc 最大的区别就是在开辟时要不要初始化,而realloc就是来调整之前开辟过的空间,空间不够就增加,空间多了就减少。malloc(需要开辟总字节大小),calloc(要分配的元素个数, 每个元素的大小),realloc(指向开辟空间的地址, 需要分配的总大小(之前的大小 + 后来需要的空间大小))。这三个动态开辟的空间,都有可能会开辟失败,失败后返回NULL空指针,因此需要提前判断它们指向的是否为空,它们都需要free来释放,同一个空间不能释放多次,free函数不能释放非动态开辟的空间,如果不用free来释放的话,会造内存泄漏(未能释放,不能再使用的内存,指在内存消失之前失去了对该段内存的控制,并非物理上的空间消失)
2.柔性数组
柔性数组说是在结构体中的最后一个元素允许是一个未知大小的数组,这就叫做柔性数组如下面定义:
struct S { int a; int arr[]; }; //此时数组arr就是一个柔性数组
(1)柔性数组的特点
1.柔性数组前面至少还有一个其他成员,不然构不成柔性数组,因此一个结构体中只存在一个柔性数组。
2.若结构体中存在柔性数组,则计算结构体的大小时,不包含柔性数组,假如我们计算上面的例子:
得到sizeof(struct S) == 4 个字节。
3.包含柔性数组成员的结构体,用malloc() 函数进行动态内存分配时,并且分配的内存大小应该大于结构体的大小,以适应我们柔性数组预期的大小。(不能直接通过 struct S s来创建变量)
为什么被称为柔性:首先柔性是我们在增容时可以让数组的空间可大可小,这一种特点称为柔性
(2)柔性数组的优势
//代码一 int main() { //这里加的是10个整型,总共开辟40+4(单位字节) struct S* pf = (struct S*)malloc(sizeof(struct S) + 40); if (pf == NULL) { perror("malloc"); return 1; } int i = 0; for (i = 0; i < 10; i++) { pf->arr[i] = i; } free(pf); pf = NULL; return 0; }
我们知道在上面动态开辟内存的过程中有一个代码是有同样的功能,如下:
//代码二 struct S { int a; int* data; }; int main() { struct S* pf = (struct S*)malloc(sizeof(struct S)); if (pf == NULL) { perror("malloc1"); return 1; } pf->data = (int*)malloc(40); if (pf->data == NULL) { perror("malloc2"); return 1; } int i = 0; for (i = 0; i < 10; i++) { pf->data[i] = i; } free(pf->data); pf->data = NULL; free(pf); pf = NULL; return 0; }
我们知道代码一和代码二产生的效果是一样的
1.首先我们为什么要先给结构体开辟,再去给pf->data去开辟,因为保证了我们所开辟的空间都在堆区上。
2.其次为什么不给pf->data直接malloc, 此时若直接给pf->data 直接malloc会导致我们的结构体在栈区,与柔性数组设计的方案不一样。
根据以上:我们知道当我们可以比较,柔性数组方案malloc次数和free次数都只有一次,而方案二的malloc和free有多次, 我们知道malloc次数多的话会导致内存碎片化,而如果忘记free的话会导致内存泄漏,因此同样功能的前提下,柔性数组的使用会更加方便。
总结优点:
1.方便内存释放
2.有利于访问速度,因为柔性数组的方式开辟的是一段连续的空间。