我们知道内存的使用方式,可以在栈区,静态区,堆区,申请空间来储存变量。
但是他们这些内存区所存储的东西是不一样的。
创建一个变量:
int a=10;int arr[]={0};——局部变量——栈区;
int a=10;int arr[]={0};——全局变量——静态区;
但是面两种申请的空间都是固定的,例如我们在使用数组的时候,有时候发现数组的申请的空间小了,又回去改数组的大小,改了之后又发现用不完,就回给我们的空间造成浪费,数组而不能达到随心所欲的修改申请给我们带来不便。这就是我们今天学习的动态内存分布就可以很好的解决相这个问题,当我们的申请的空间小了,我们可以直接扩充,用不完的可以直接减少。
1.malloc
头文件: #include<stdlib.h>
函数框架:void*malloc(size_t size)。
返回值:函数的返回类型位空指针类型,可根据具体的申请的和需要强行转换指针类型,返回的指针指向申请的空间的是首地址。
参数:函数的参数是一个无符号整型数字(size_t size),代表申请空间的大小,单位为字节。
如果申请成功函数返回申请在堆区空间的地址,如果申请不成功,就会返回NULL(空指针)。
实例:
#include<stdio.h> #include<stdlib.h> int main() { int* p = (int*)malloc(sizeof(int) * 10);//申请了一个可以存放10个int类型的空间; if (p == NULL)//判断是否申请失败,申请失败会返回空指针(NULL). { printf("申请失败\n"); } else { printf("申请成功\n"); } return 0; }
这里申请成功了
让我们看一下申请不成功的情况
#include<stdio.h> #include<stdlib.h> int main() { int* p = (int*)malloc(100000000000000000*10000000000000000000);//申请了一个非常大的空间, //已经超出我们堆区的最大存储空间, //肯定会申请失败; if (p == NULL)//判断是否申请失败,申请失败会返回空指针(NULL). { printf("申请失败\n"); } else { printf("申请成功\n"); } return 0; }
接下来我们看一下malloc申请空间的使用:
看代码:
#include<stdio.h> #include<stdlib.h> int main() { int* p = (int*)malloc(sizeof(int)*10);//申请可以存放10个int型变量的空间; if (p == NULL)//判断是否申请失败,申请失败会返回空指针(NULL). { printf("申请失败\n"); return 0;//如果申请失败,我们程序就直接退出,因为申请失败也没有必要进行操作; } else { int i = 0; for (i = 0; i < 10; i++) { *(p + i) = i;//申请的10个空间,赋值0——9; } for (i = 0; i < 10; i++) { printf("%d ", *(p+i));//证明我们我们的数字已经存进去,打印出我们存进去的数字。 } } free(p);//当我们的动态申请的空间不再使用的时候,就应该还给操作系统,所以就是用free,对申请的空间释放。 p = NULL;//但是我们申请的空间是还给操作系统了,但是指针 p 仍旧可以找到那块空间,所以我们把 p赋值成空指针,将指针 p 与空间完全断开联系。 return 0; }
关于free:
malloc与free,是成对使用的。
2.calloc
头文件:#include<stdlib.h>
函数框架:void*calloc(size_t num,size_t size);
calloc的功能与malloc基本一样,也是从堆区申请一块空间,但是区别就在于calloc会在返回地址之前把申请的空间全部赋值为0;
参数:size_t num——无符号整形数,代表申请空间的个数,size_t size——无符号整形数,代表
申请每个空间的大小,单位为字节。
返回值:函数的返回类型位空指针类型,可根据具体的申请的和需要强行转换指针类型,返回的指针指向申请的空间的是首地址。
如果申请成功函数返回申请在堆区空间的地址,如果申请不成功,就会返回NULL(空指针)。
实例2.
#include<stdio.h> #include<stdlib.h> int main() { int* p = (int*)calloc(10,sizeof(int));//申请可以存放10个int型变量的空间; if (p == NULL)//判断是否申请失败,申请失败会返回空指针(NULL). { printf("申请失败\n"); return 0;//如果申请失败,我们程序就直接退出,因为申请失败也没有必要进行操作; } else//申请成功开始使用 { int i = 0; for (i = 0; i < 10; i++) { printf("%d ", *(p + i));//查看在为赋值的时候申请的空间里面存储是不是0; *(p + i) = i;//申请的10个空间,赋值0——9; } printf("\n"); for (i = 0; i < 10; i++) { printf("%d ", *(p + i));//证明我们的数字已经存进去,打印出我们存进去的数字。 } } free(p); p = NULL;//用完别忘记释放哦。 return 0; }
3.realloc
realloc函数使我们动态内存管理更加灵活。
有时会发现过去申请的空间太小了,有时候我又会觉得申请的空间太大了,那为了合理的内存,我们呢一定会对内存进行灵活的调整,realloc就可以做到对内存大小的调整。
函数原型:void* realloc(void*ptr,size_t size)
参数:void*ptr ,ptr为要调整的空间的地址,size_t size为调整之后的新大小。
返回值:调整之后的内存的起始位置,
函数在调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间里面去。
realloc使用实例:
#include<stdio.h> #include<stdlib.h> int main() { int* p = (int*)calloc(10, sizeof(int));//申请可以存放10个int型变量的空间; if (p == NULL)//判断是否申请失败,申请失败会返回空指针(NULL). { printf("申请失败\n"); return 0;//如果申请失败,我们程序就直接退出,因为申请失败也没有必要进行操作; } else//申请成功开始使用 { int i = 0; for (i = 0; i < 10; i++) { printf("%d ", *(p + i));//查看在为赋值的时候申请的空间里面存储是不是0; *(p + i) = i;//申请的10个空间,赋值0——9; } printf("\n"); for (i = 0; i < 10; i++) { printf("%d ", *(p + i));//证明我们的数字已经存进去,打印出我们存进去的数字。 } } printf("\n"); //这时我们发现内存不够 int* p1 = (int*)realloc(p, sizeof(int) * 15);//我们将申请空间扩大到可以存放15个int型数字。 if (p1 == NULL) { printf("申请失败"); return 0; } for (int i = 10; i < 15; i++) { *(p1 + i) = i;//将新申请的空间赋值10——14; } for (int i = 0; i < 15; i++) { printf("%d ", *(p1 + i)); } free(p1); p1 = NULL; return 0; }
关于realloc的注意:
如果原空间之后有足够的空间就可以直接追加,然后返回调整之后的空间的起始位置地址;
如果原空间后面没有足够的空间可以用来追加,则realloc就会重新找一个新的内存区域,将元数据拷贝过去再追加,最后返回新空间的起始位置地址;
要用一个新的指针变量来接受realloc函数的返回值;
关于realloc在内存追加空间的时候,后面没有足够的大小用来追加会重新找一个新的内存区域,将元数据拷贝过去再追加,最后返回新空间的起始位置地址;
实例:
#include<stdio.h> #include<stdlib.h> int main()//(1)后面有足够的空间追加; { int* p = (int*)malloc(1);//申请可以存放1个字节的空间; if (p == NULL)//判断是否申请失败,申请失败会返回空指针(NULL). { printf("申请失败\n"); return 0;//如果申请失败,我们程序就直接退出,因为申请失败也没有必要进行操作; } printf("%p\n", p); int* p= (int *)realloc(p, 2);//追加一个字节,后共2个字节; printf("%p\n", p); free(p); p=NULL; return 0; }
#include<stdio.h> #include<stdlib.h> int main()//(1)后面没有足够的空间追加; { int* p = (int*)malloc(1);//申请可以存放1个字节的空间; if (p == NULL)//判断是否申请失败,申请失败会返回空指针(NULL). { printf("申请失败\n"); return 0;//如果申请失败,我们程序就直接退出,因为申请失败也没有必要进行操作; } printf("%p\n", p); int* p= (int *)realloc(p, 10000);//追加字节,后共10000个字节; printf("%p\n", p); free(p); p=NULL; return 0; }
常见的动态内存的错误
1.对空指针解引用
void test() { int* p = (int*)malloc(1000000000 * 10000000*10000000);//由于申请的空间过大而申请失败返回空指针; *p = 20;//当p为NULL时就会有问题; free(p); p = NULl; }
2.对动态开辟的空间越界访问
int* p = (int*) = malloc(sizeof(int) * 5);//申请5个int的空间 if (p = NULl) { exit; } for (int i = 0; i < 10; i++)//却访问10个int的空间 { *(p + i) = i; }
3.对非动态开辟的空间进行free释放
void test() { int a = 10; int* P = &a; free(p);//OK? }
4.使用free释放一块空间的一部分
void Get() { int*p = (char*)malloc(100); p++; free(p);//p不在指向动态内存的起始位置; }
5.对同一块动态内存多次释放
void Get() { int*p = (char*)malloc(100); free(p); free(p); }
6.动态内存忘记释放
7.
void Get(char* p) { p = (char*)malloc(100); } void test() { char* str = NULL; Get(str);//以变量的方式传参;无法做到str的修改; strcpy(str, "hello"); printf(str); }
运行的时候程序会崩溃,而且存在内存泄漏;
str以值的形式传参给p;p是Get的函数的形参,只能在函数内部有效,等Get函数返回之后,动态开辟的空间还未释放,并且无法找到,就会存在内存泄漏。