一、为什么会有动态内存分配
1.1向内存申请空间的两种形式:
在栈空间上申请空间(第一种:空间大小不可变):
----特点:申请时必须指明长度,一旦申请成功,空间大小不能改变。
int a = 0; //向栈空间申请4个字节大小空间
char b = 'a'; //向栈空间申请1个字节空间
char arr[10] ={ 0 }; //向栈空间连续申请10个char类型的大小空间(10个字节)
int s[10] = { 0 }; //向栈空间连续申请10个int类型大小的空间(40个字节)
在堆区上申请空间(第二种:空间大小可以改变):
二、动态内存开辟的函数:malloc
函数原型:void* malloc(size_t size);
1.函数返回类型是void*(指针类型);void*可以指向任何类型的指针;但是void类型指针不是进行解引用,必须进行强制类型转换才能进行解引用;
2.size是在堆区申请的空间大小,单位是字节;
例如:int *p=(int*)malloc(sizeof(int));
指在堆空间申请大小为4个字节的空间,并把首地址强制类型转换成整形(int)指针,然后赋给整形指针变量p;
3.头文件:
#include<stdlib.h>
注意事项:
1.申请(开辟)成功:会返回指向开辟空间的指针;
2.申请(开辟)失败:会返回一个NULL类型的指针,所以一定要对malloc的返回值进行检查;
3.返回值的类型为void*,所以malloc函数只管开辟空间,但是不知道空间的类型;具体在使用的时候由使用者决定;
4.当size为0时;malloc的行为是未被定义的,大小取决于编译器,可能申请成功,可能申请失败;
三、free函数
c语言中,提供一个一个函数free,用来释放动态内存空间,即申请了动态内存空间,就要去释放动态内存空间。
函数原型:void free(void * str);
void*函数可以接受任意类型的地址,比如:int*,char*。
注意事项:
1.如果str指向的内存不是动态的,free的行为是未被定义的。
2.如果str是NULL指针,则free什么事情都不做。
3.malloc和free的声明都包含在头文件#include<stdlib.h>中。
4.free函数只会对内存进行释放,开始的指针任然还在,最后要将他变为NULL;(p=NULL)
让我们看看下面代码:
#include<stdio.h>
#include<stdlib.h>
#include<limits.h>
int main()
{
int* p = (int*)malloc(10* sizeof(int)); //申请一块很大的动态空间
if (p == NULL) //检查是否开辟成功,开辟失败,p为NULL
{
perror("malloc");
return 1; //非正常退出
}
printf("%p\n", p);
free(p);
printf("%p\n", p);
p = NULL;
printf("%p\n", p);
return 0;
}
5.str是要释放内存的起始地址,下面看一段代码,判断是否正确?
#include<stdio.h>
#include<stdlib.h>
int mian()
{
int* p = (int*)malloc(sizeof(int) * 10);
if (p ==NULL)
{
perror("malloc");
return 1;
}
for (int i = 0; i < 10; i++)
{
*p = i + 1;
p++;
}
free(p);
p = NULL;
return 0;
}
for循环中,p每次++,最后对p进行内存释放,是不正确的,因为此时的p不指向最上面开辟的动态内存的起始地址。
四、calloc函数
函数原型:void* calloc(size_t num,size_t size);
声明包含于#include<stdlib.h>
• 函数的功能是为 num 个大小为 size 的元素开辟⼀块空间,并且把空间的每个字节初始化为0。
• 与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全为0。
•如果以后要对字节初始化为0,那么就使用calloc,不用初始化,就使用malloc。
五、realloc函数
函数原型:void* realloc(void* str,size_t size);
• realloc函数的出现让动态内存管理更加灵活,可以调整malloc和calloc动态空间的大小。
• 有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的时 候内存,我们⼀定会对内存的大小做灵活的调整。那 realloc 函数就可以做到对动态开辟内存大小的调整。
#include<stdio.h>
#include<stdlib.h>
int main()
{
int* p = (int*)calloc(3, 4); //申请3个大小为4个字节的空间
if (p == NULL)
{
perror("calloc");
return 1;
}
for (int i = 0; i < 3; i++)
{
p[i] = i + 1;
}
//1 2 3
int* str=(int*)realloc(p, 4 * sizeof(int));
if (str != NULL) //对申请的地址进行检查
{
p=str;
*(p+3) = 4; //1 2 3 4
for (int i = 0; i < 4; i++)
{
printf("%d ", *(p + i));
}
free(p);
p = NULL;
}
return 0;
}
realloc函数调整空间的两种情况:
1.如果开始创建的空间后面有足够的空间,那就在后面新增空间。
2.如果后面空间不够,那就在堆区新找一块新的空间,返回新的起始地址。并且会把开始的数据拷贝到新的空间里,把原来的空间自动释放。
3.如果上面两种都不行,则申请失败,返回NULL。
所以,我们使用realloc函数时,如果直接把地址传给原来的p指针,如果申请失败,那么就找不到原来的内存,这时候就会发生内存泄漏。
因此我们应该先用新创建一个新的指针,如果指针不是NULL,则再赋给p。
六、常见的动态内存错误
1.对NULL进行解引用操作。
#include<stdio.h>
#include<stdlib.h>
int main()
{
int* p = (int*)(INT_MAX);
*p = 1;
free(p);
p = NULL;
return 0;
}
没有对p指针进行检查,就对p进行解引用操作,因为如果申请失败,p可能为NULL。
解决办法:用if进行判断,或者用assert(p)进行判断。
2.对动态开辟的空间的越界访问。
3.对非动态开辟的空间进行free释放。
#include<stdio.h>
#include<stdlib.h>
int mian()
{
int a = 0;
int* pa = &a;
free(pa); //对非动态内存进行释放
pa = NULL;
return 0;
}
4.使用free释放动态内存的一部分。
#include<stdio.h>
#include<stdlib.h>
int mian()
{
int* p = (int*)malloc(4 * sizeof(int));
if (p == NULL)
{
perror("malloc()");
return 1;
}
for (int i = 0; i < 3; i++)
{
*p = i + 1;
p++;
}
free(p);
p = NULL;
return 0;
}
free(p)没用把malloc申请的动态内存空间全部释放。for循环中,p向后移了3位。不再是动态内存的起始位置。
5.对同一块动态内存进行多次释放。
#include<stdio.h>
#include<stdlib.h>
int mian()
{
int* p = (int*)malloc(4 * sizeof(int));
if (p == NULL)
{
perror("malloc()");
return 1;
}
free(p);
free(p); //对动态内存内存进行多次释放
return 0;
}
解决办法:如果我们可以再第一次free(p)后面把p变为NULL。那么后面的free就什么都不做。
6.动态内存空间忘记释放(内存释放)。
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
void test()
{
int* p = (int*)malloc(3 * sizeof(int));
if(p==NULL)
{
assert(p);
}
for (int i = 0; i < 3; i++)
{
*(p + i) = i + 1;
printf("%d ", *(p + i));
}
}
int mian()
{
test();
return 0;
}
该代码中,p创建的是局部变量,当test函数运行结束时,p就会销毁,但是在test()函数中,没有进行动态内存释放。如果我们再想在main函数中对动态内存释放,我们是不能找到这一块空间的,这个时候就会放生内存泄漏,如果内存释放过多,内存就会被耗干。
*如果发生内存释放,如果有内存泄漏,当程序结束的时候,操作系统会进行回收。
*解决办法:malloc函数与free函数成对出现。
malloc函数成对出现也不一没有内存泄漏,因为可能虽然成对出现,但是有一些语句没有执行,被一些语句提前返回了。
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
int mian()
{
int* p = (int*)malloc(5 * sizeof(int));
if (p == NULL)
{
perror("malloc()");
return 1;
}
*p = 0;
if (*p == 0)
{
return 0;
}
free(p);
p = NULL;
return 0;
}
七、柔性数组
定义:结构体中最后一个元素允许时位置大小的数组
注意:
1.柔性数组前必须有其他成员!!!
2.sizeof(结构体)的结果,不包含柔性数组的大小
3.柔性数组一般用malloc开辟空间时,(struct S*)malloc(sizeof(struct S) + 40);
40为柔性数组的大小。
4.malloc开辟空间时
实例:
struct S
{
int n;
int arr[];//柔性数组
};
struct S
{
int n;
int arr[0];//柔性数组
};
int main()
{
//printf("%d\n", sizeof(struct S));
struct S* ps = (struct S*)malloc(sizeof(struct S) + 40);
if (ps == NULL)
{
perror("malloc");
return 1;
}
ps->n = 100;
int i = 0;
for (i = 0; i < 10; i++)
{
ps->arr[i] = i + 1;
}
//空间不够,需要增容
struct S* ptr = realloc(ps, sizeof(struct S) + 60);
if (ptr == NULL)
{
perror("realloc");
return 1;
}
ps = ptr;
ps->n = 15;
for (i = 0; i < 15; i++)
{
printf("%d\n", ps->arr[i]);
}
//释放
free(ps);
ps = NULL;
return 0;
}