我们目前知道内存中几个常见的区
1.栈区
2.堆区
3.静态区
………
我们先来看看目前已知的在内存中是如何开辟空间的
int a;//向栈区申请4个字节的内存
int arr[5];//向栈区申请20个字节的内存
我们会发现,数组的开辟多少空间是要提前设置好的,那么我们会不会有时候确定不了需要的字节呢
我们来举一个例子
struct Stu
{
char name[20];
int age;
int scort[10];
}
我们创建了一个有十个元素的整形数组用来存放学生的成绩,我们就向内存申请了40个字节用来存放成绩。
但是如果我们需要录入的成绩有12门怎么办,或者只有5门成绩那么剩下的内存空间该何去何从?
有的人会有这样的想法,我们可不可以这样
int n;
scanf("%d",&n);
int scort[n];
我们创建一个变量来让用户自己确定数组大小,输入5就创建一个5个元素的数组。
虽然理想很丰满,但是现实很骨感(目前只有在c99支持这样的创建)。
所以这个方法就无法在所有的编译器中实现
这时我们就要引入一个全新的知识
动态内存
1.动态内存函数
(1)malloc和free
C语言提供了一个动态内存开辟的函数:
void* malloc (size_t size);
参数类型:size_t —-无符号整型,size代表你需要向内存申请的字节数
返回类型:void* —–返回申请的内存的地址
我们来看一个实际例子
int* p=(int*)malloc(10*sizeof(int));
我们向内存申请了一块40个字节的空间用来存放10个整型,我们将返回的地址强制转换为整形后存放到指针p中。
当然我们既然是申请,那么肯定会有两种情况
1.申请成功——-返回申请的内存空间的地址
2.申请失败——-返回空指针NULL
所以我们如果去使用这块内存应该是这样的
int* p=(int*)malloc(10*sizeof(int));
int i = 0;
if(p==NULL)
{
printf("申请失败\n");
}
else
{
for(i = 0;i < 10;i++)
{
*(p+i)=i;
printf("%d ",*(p+i));
}
}
因为我们刚才说了有可能会出现内存不足从而申请失败的时候会返回空指针
所以我们在使用前需要去判断这块空间到底有没有申请下来
所以我们用到了if语句去判断
那么申请完了就结束了吗?
当然没有,俗话说的好:有借有还,再借不难。用完后你就应该把这块空间还给内存,不然你会发现多申请几次就用完了
所以我们引入下个函数
void free (void* ptr);
free函数用来释放动态开辟的内存。
我们看参数部分:void* ptr是一个指针,传入你需要释放的空间的地址
所以刚才的代码是有错误的,正确的应该是这样
int* p=(int*)malloc(10*sizeof(int));
int i = 0;
if(p==NULL)
{
printf("申请失败\n");
}
else
{
for(i = 0;i < 10;i++)
{
*(p+i)=i;
printf("%d ",*(p+i));
}
}
free(p);
p=NULL;
我们在使用完后就把空间释放掉,然后把原来存放开辟空间地址的指针赋为空指针
当然这个free也有需要注意的地方
1.如果参数ptr指向的空间不是动态开辟的,那free函数的行为是未定义的。
2.如果参数ptr是NULL指针,则函数什么事都不做。
2 calloc C语言还提供了一个函数叫calloc
calloc函数也用来动态内存分配。原型如下:
void* calloc (size_t num, size_t size);
相比于malloc,我们发现这个函数的参数部分有两个
第一个:表示元素数量
第二个:表示每个元素的大小
那么刚才申请内存的方式就应该变成这样
int* p=(int *)calloc(10,sizeof(int));
但是calloc肯定不止这一点不同,不然他的实现都可以用malloc
那么calloc最重要的不同在于:calloc会在返回地址之前把申请的空间的每个字节初始化为全0。
3 .realloc
那么我们在申请完后发现不够怎么办,再去申请一次吗?
当然不是,我们引入新函数
void* realloc (void* ptr, size_t size);
void* ptr 表示我们需要进行调整的内存的地址
size_t size 表示调整完后的大小
返回值为调整之后的内存起始位置。
这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间。
学习完动态内存的开辟后
那么我们来看看常见的错误吧
1 .对NULL指针的解引用操作
void test()
{ int *p = (int *)malloc(INT_MAX/4);
*p = 20;//如果p的值是NULL,就会有问题 free(p);
}
我们知道申请内存会有申请失败的情况,此时p存放的就是失败后返回的NULL
此时对p进行解引用操作,就会出现问题。
所以我们在使用时最好判断一下是否申请成功
2 .对动态开辟空间的越界访问
void test()
{ int i = 0;
int *p = (int *)malloc(10*sizeof(int));
if(NULL == p)
{
exit(EXIT_FAILURE);
}
for(i=0; i<=10; i++)
{
*(p+i) = i;//当i是10的时候越界访问
}
free(p);
}
当你只申请了40个字节,但是你在访问的时候访问到了第41个字节的时候,此时指针为野指针,会造成非法访问的操作。
3 对非动态开辟内存使用free释放
void test()
{
int a = 10;
int *p = &a;
free(p);
}
p此时存的是a的地址,但是a的值存放在栈区,而free释放的是堆区
所以会造成错误
4. 使用free释放一块动态开辟内存的一部分
void test()
{
int *p = (int *)malloc(100);
p++;
free(p);
}
我们会发现p进行了自增操作,此时p指向的并不是申请的那块内存的初始位置,此时释放内存只释放掉了p后面的部分,所以是错误的
5 .对同一块动态内存多次释放
void test()
{
int *p = (int *)malloc(100);
free(p);
free(p);
}
6 .动态开辟内存忘记释放(内存泄漏)
void test()
{ int *p = (int *)malloc(100);
if(NULL != p)
{
*p = 20;
}
}
int main()
{
test();
while(1);
}
忘记释放不再使用的动态开辟的空间会造成内存泄漏。
切记:动态开辟的空间一定要释放,并且正确释放