一、为什么要有动态内存分配
我们在写代码时,编译器都会自动为我们分配空间,但会有这么两个特点:
空间开辟⼤⼩是固定的。
数组在申明的时候,必须指定数组的⻓度,数组空间⼀旦确定了⼤⼩不能调整。
但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间⼤⼩在程序运⾏的时候才能知道,那数组的编译时开辟空间的⽅式就不能满⾜了。
C语⾔引⼊了动态内存开辟,让程序员⾃⼰可以申请和释放空间,就⽐较灵活了。
二、动态内存开辟的函数
2.1 malloc函数
void* malloc (size_t size);
这个函数向内存申请⼀块连续可⽤的空间,并返回指向这块空间的指针。
如果开辟成功,则返回⼀个指向开辟好空间的指针。
如果开辟失败,则返回⼀个 NULL 指针,因此malloc的返回值⼀定要做检查。
返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使⽤的时候使⽤者⾃ ⼰来决定。
如果参数 size 为0,malloc的⾏为是标准是未定义的,取决于编译器。
2.2 calloc函数
void* calloc (size_t num, size_t size);
函数的功能是为 num 个⼤⼩为 size 的元素开辟⼀块空间,并且把空间的每个字节初始化为0。
与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0。
2.3 realloc函数
void* realloc (void* ptr, size_t size);
realloc函数的出现让动态内存管理更加灵活。
有时会我们发现过去申请的空间太⼩了,有时候我们⼜会觉得申请的空间过⼤了,那为了合理的时 候内存,我们⼀定会对内存的⼤⼩做灵活的调整。那 realloc 函数就可以做到对动态开辟内存⼤ ⼩的调整。
realloc在调整内存空间的是存在两种情况:
情况1:原有空间之后有⾜够⼤的空间
情况2:原有空间之后没有⾜够⼤的空间
情况1
当是情况1 的时候,要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发⽣变化。
情况2
当是情况2 的时候,原有空间之后没有⾜够多的空间时,扩展的⽅法是:在堆空间上另找⼀个合适⼤⼩ 的连续空间来使⽤。这样函数返回的是⼀个新的内存地址。
三、内存释放函数
学习完内存开辟函数后,我们随后会来学习内存释放函数。那我们为什么要学习内存释放函数呢?内存开辟函数就是向内存中借一块空间,俗话说:有借有还。所以,我们要来学习内存释放函数来进行归还。所以在学习前记住一句话:只要开辟就要释放。
void free (void* ptr);
内存释放函数为free函数,如果参数 ptr 指向的空间不是动态开辟的,那free函数的⾏为是未定义的。
如果参数 ptr 是NULL指针,则函数什么事都不做。
四、柔性数组
柔性数组是什么?相信看到这你有点小懵,难道会柔道的数组称之为柔性数组?非也。柔性数组为结构体中定义的处于结构体最后一个成员的数组(最好不要定义其大小,有的编译器会报错)。
1. typedef struct s 2. { 3. int a; 4. int arr[]; 5. };
柔性数组的特点:
柔性数组的前面必须包含其他结构体成员。
sizeof求其结构体大小时不会包含柔性数组的大小。
使用malloc为柔性数组开辟空间时,要大于结构体的大小以符合柔性数组的期待。
用法如下:
1. 2. #include<stdio.h> 3. typedef struct s 4. { 5. int a; 6. int arr[]; 7. }type_a; 8. int main() 9. { 10. int a = 0; 11. type_a* p = (type_a*)(malloc(sizeof(type_a) + 100 * sizeof(int))); 12. p->a = 100; 13. for (int i = 0; i < 100; i++) 14. { 15. p->arr[i] = i; 16. } 17. free(p); 18. p = NULL; 19. return 0; 20. } 21. 22.
五、 经典例题
1. #include<stdio.h> 2. #include<stdlib.h> 3. void GetMemory(char* p) 4. { 5. p = (char*)malloc(100); 6. } 7. void Test(void) 8. { 9. char* str = NULL; 10. GetMemory(str); 11. strcpy(str, "hello world"); 12. printf(str); 13. }
以上代码运行会有什么后果?
本题意图为为str开辟出一块空间,对吧?但是,在函数中p为局部变量,运行结束时会销毁,所以,它的地址传不回来,就会开辟失败。但,即使开辟成功,仍有问题,这是为什么呢?咱们在开始时,说过了要有借有还,所以应该要对其进行内存释放,否则,会造成内存泄漏。
1. #include<stdio.h> 2. #include<stdlib.h> 3. void Test(void) 4. { 5. char* str = (char*)malloc(100); 6. strcpy(str, "hello"); 7. free(str); 8. if (str != NULL) 9. { 10. strcpy(str, "world"); 11. printf(str); 12. } 13. }
以上代码运行会有什么后果?
在str开辟完空间后,又对其释放,那str能打印出来吗?答案是:不一定。为什么?内存不是没了吗?那里面存放的理应没了呀?对吧。其实,你可以这样理解:你在酒店开了个房间,第二天为了赶火车,不小心把身份证落下了,如果此时房间还没有被打扫,你还能在原位置找到,对吧?打扫了就不能在原位置找到了对吧?理解了我举得例子,相信你就理解了打印结果的情况。
完!