目录
春秋蝉在耳边鸣 无人与我并肩行
永生路上多坎坷 这路一人也走的
✨1.什么是动态内存分配
我们在处理内存时,内存区域主要分为三块区域,分别为:栈区,堆区,静态区
而我们经常使用的语句其实都是在栈区开辟空间的,如:
int arr[10];
char str[20];struct S s[10];
int a;
float b;
char c;
但是使用这样的声明,在栈区开辟空间时是有一定风险的
存在风险:
1. 开辟出来的空间是固定的,不能进行具体字节大小的修改,所以可能浪费掉栈区的空间大小
2. 栈区空间开辟太大可能导致栈溢出
因此,我们可以将一些东西存储在堆区中,堆区的空间是本身就存在的,我们要做的就是分配出来并利用这些空间,因此也叫做动态内存分配
动态内存的分配是在堆区完成的,同时,堆区的内存空间几乎于无限大,因此不必担心如栈溢出的问题
动态内存分配是需要调用动态内存函数实现的,下面介绍四种内存函数
点击超链接可以进行访问函数
💕2.动态内存开辟函数 malloc
#include<stdlib.h> void* malloc(size_t size) //以上是malloc函数的头文件以及参数
malloc 函数会返回一个void*类型的指针,这个指针是 malloc开辟空间大小的首地址
size表示空间大小是size个字节
动态内存函数 malloc 可以在堆区开辟出一定的空间大小供程序员使用
1. malloc如果开辟成功,会返回所开辟空间的首地址
2. malloc如果开辟失败,就会返回空指针(NULL指针)
我们无法确定malloc函数100%开辟成功,所以在利用malloc函数开辟空间时一定要进行检查
使用效果如下:
在使用malloc函数开辟空间时,因为malloc函数返回的地址是void*类型,因此我们需要根据自己的实际需要,进行地址类型的转换
如图:我们用malloc在堆区开辟了40个字节的内存空间大小,解引用p一次访问4个字节的大小
但我们会发现,我们使用malloc函数开辟的空间大小,没有解引用的部分都不会初始化值为0,返回全部都是cd,这是因为malloc函数在开辟空间大小时不会初始化内容
但我们这样使用malloc函数其实是不对的,之前我们强调过,malloc函数的使用一定要进行检查,否则编译器就会出现警告,如下图:
那么我们该如何解决呢?
✨3.malloc函数的检查(两种方法)
我们使用完malloc函数开辟内存后,因为无法100%确定开辟成功,所以我们需要对malloc函数是否开辟成功进行检查
检查的两种方法:
1. 利用assert函数进行检查
2. 使用if语句进行检查
1.assert情况检查
#define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<stdlib.h> #include<assert.h> #define NDEBUG int main() { int* p = (int*)malloc(40); assert(p != NULL); *p = 20; printf("%d", *p); }
malloc函数开辟失败就会返回空指针,因此只需要判断是否为空指针即可,如果为空指针,assert就会报错并停止运行代码(使用assert不用担心 NDEBUG 注释掉)
但使用assert有一点不好,如果我们再使用assert函数断言其他代码的同时,确定完malloc函数开辟成功后不能使用#define NDEBUG 注释掉assert函数,因为无法判断下一次执行代码malloc是否会开辟成功,这样就会影响代码的运行速度。可以使用assert函数,并不推荐用
2.if语句判断
#define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<stdlib.h> int main() { int* p = (int*)malloc(40); if (p == NULL) { perror("malloc"); return 1; } *p = 20; printf("%d", *p); }
使用 if 语句,只需要判断是否为空指针即可,如果为空指针,我们可以使用perror函数打印出错误信息,并及时结束代码返回1,推荐使用 if 语句
💕4.动态内存释放函数 free
我们在堆区开辟完空间后,我们可以使用 free 函数释放掉开辟的内存,并将这块内存空间还给操作系统
#include<stdlib.h> void free(void* ptr)
free函数接收一个因动态内存函数所开辟内存空间的首地址
注意:
1. free函数接受的地址一定是动态内存在堆区开辟空间的地址,不能用栈区的地址
2.free函数所接受的地址一定是动态内存开辟堆区地址的首地址
以上两种情况,如果实现任意一种,代码直接崩
free函数正确示例如下:
free 释放前的内存
free释放后的内存
注意:释放空间之后,free函数接收的指针(也就是因动态内存分配出的空间的首地址)就没有任何意义,但 free 不会自动给它置成空指针,此时他就成为一个野指针,所以我们要即及时将其置成空指针,如下:
#define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<stdlib.h> int main() { int* p = (int*)malloc(40); if (p == NULL) { return 1; } free(p); p == NULL; //初始化为空指针 //野指针在使用时是非常危险的,因为你不知道它指向哪里,所以要及时定义为空指针 }
✨5.free 函数接收空指针
free函数在接受空指针时会进行摆烂状态,什么也不做,如下:
✨6.为什么要释放动态内存
在我们利用动态内存分配出来的空间后,这些分配出来的空间只有两种方法可以释放掉
1. 利用free函数,将动态内存分配出来的空间释放掉
2. 等待程序自己结束,程序结束时会将这些空间释放掉
所以及时的释放掉堆区内存是非常重要的
💕7.动态内存开辟函数calloc
calloc函数与malloc函数都为开辟动态内存的函数,两者极其相似却有不同,接下来进行讲解
#include<stdlib.h> void* calloc (size_t num, size_t size);
calloc函数的第一个参数 num 为元素的个数
第二个参数 size 为每个元素字节的大小
calloc函数与malloc函数相同点:
如果开辟成功,会返回所开辟空间中较小空间的地址
如果开辟失败,就会返回空指针(NULL指针)
我们无法确定calloc函数100%开辟成功,所以在利用malloc函数开辟空间时一定要进行检查
calloc函数与malloc函数不同点
1. 参数不同
2. calloc函数在开辟完内存空间后,会将内存空间中每个字节全部初始化为0
效果如下:
可见,calloc与malloc函数相似,开辟的所有内存,每一个字节都会初始化为0
在free方面用法相同
💕8.动态内存调整函数 realloc
虽然我们能通过 malloc 或 calloc 函数动态内存分配一块空间,但这块空间分配完成后大小也是固定的,如果空间满了需要扩容或空间多了需要缩减,这时 realloc 函数就登场了
realloc 的作用是对已经动态分配的一块空间再次分配。
#include<stdlib.h> void* realloc (void* ptr, size_t size);
它有两个参数:
- ptr:要调整的内存地址,这块内存是动态内存分配得到的,必须是首地址,如果不是首地址代码运行就会崩
- size:以字节为单位的新大小
它会返回调整之后的内存起始位置。
关于调整之后的内存起始位置会出现以下两种情况:
1. 与原来内存的起始位置相同
2. 与原来内存的起始位置不同
当要缩小原有的内存时,说明原来的内存空间已经足够,此时它的返回值就是原来内存的起始位置。
当要扩大原有的内存时,又有两种情况:
1. 原有空间之后有足够大的内存时,直接在原内存的基础上再开辟后边几个连续的空间,此时它的返回值是原来内存的起始位置;
2. 原有空间之后没有足够大的内存进行扩容时,此时会在堆空间上另找一个合适大小的连续空间来使用,这样函数返回的是一个新的内存地址,与原来内存的起始位置不同。这时会拷贝原来空间的内容到新的空间的相应位置,原空间就被释放掉了。
虽然情况这么多,但其实很好更改与理解
所以,在使用 realloc 函数时就要注意用一个临时指针接收,当内存调整成功后再将临时指针赋给原指针
代码如下:
#define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<stdlib.h> int main() { int* p = (int*)malloc(50); if (p == NULL) { return 1; } int * ptr = (int*)realloc(p, 30); if (ptr != NULL) { p = ptr; } free(p); p = NULL; }
realloc函数调整前:
realloc函数调整后:
realloc函数在有一种情况下等价于malloc
代码如下:
#define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<stdlib.h> int main() { int* p = (int *)realloc(NULL, 40); if (p == NULL) { return 1; } free(p); p = NULL; }
这种情况下,就相当于开辟了40个字节的空间大小
✨9.动态内存分配——分配数组
9.1 动态内存分配——一维数组
利用动态内存分配出一维数组是较容易的,我们知道一维数组在内存中存储是连续的,我们可以利用这一点,动态内存分配出连续的空间,然后进行赋值利用,就实现了动态内存分配一维数组
代码如下:
#define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<stdlib.h> int main() { int* p = (int*)malloc(40); if (p == NULL) { return 1; } for (int i = 0; i < 10; i++) { p[i] = i; } for (int i = 0; i < 10; i++) { printf("%d ", p[i]); } free(p); p = NULL; }
可能不懂的点:
1. p[ i ] = *(p+i)
9.2 动态内存分配——二维数组
使用动态内存分配出二维数组,我们有三种方法
第一种:利用二维数组其实是一堆一维数组的原理,开辟足够多的一维数组,并当作二维数组使用
#include<stdio.h> #include<stdlib.h> #include<assert.h> #define row 8 #define line 5 int main() { int * p = (int*)malloc(row*line*sizeof(int)); //或者int * p = (int*)malloc(160*sizeof(char)); //开辟40个int类型的空间大小 int count = 1; assert(p!=NULL); for(int i = 0;i<row;i++){ for(int j = 0;j<line;j++){ *(p+(i*line)+j) = count++; } } for(int i = 0;i<row;i++){ for(int j = 0;j<line;j++){ printf("%d ", *(p+(i*line)+j)); } printf("\n"); } free(p); p = NULL; }
第二种:开辟4个 int* 类型的空间大小,让每一个 int* 都指向 5个int 的首地址
#define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<stdlib.h> #include<assert.h> int main() { int** p = (int**)malloc(4 * sizeof(int*)); assert(p != NULL); for (int i = 0; i < 4; i++) { *(p + i) = (int*)malloc(5 * sizeof(int)); assert(p + i != NULL); } int count = 1; for (int i = 0; i < 4; i++) { for (int j = 0; j < 5; j++) { assert((*(p + i)) + j != NULL); //*(*(p + i) + j * sizeof(char)) = count++; *((*(p + i)) + j) = count++; } } for (int i = 0; i < 4; i++) { for (int j = 0; j < 5; j++) { assert((*(p + i)) + j != NULL); //*(*(p + i) + j * sizeof(char)) = count++; printf("%d ", * ((*(p + i)) + j) ); } printf("\n"); } //释放每个int*所代表的5个int for (int i = 0; i < 4; i++) { free(*(p + i)); *(p + i) = NULL; } //释放一级指针 free(p); p = NULL; }
这里一定要都释放,只释放 int* 的空间并不会释放 int 的空间