目录
1.动态内存管理的原因
int a = 0;//在栈空间上开辟4个字节
int arry[10];//在栈空间上开辟40个字节的连续空间
上述内存开辟的特点:
(1)内存开辟的大小是固定的
(2)数组在申明的时候,必须指定数组的长度,数组空间的大小被确定之后就不能被改变
显然,当大小被固定的时候,或多或少都存在着空间大小不足或者浪费的情况
C语言引入了动态内存开辟,让程序员自己申请和释放空间,就非常灵活了
2.malloc和free
2.1malloc
malloc函数的功能是在内存的动态存储区中分配一块指定大小的连续内存空间
I 传入的参数指的是想要被分配的空间大小,单位是字节;
II返回值
分配成功:返回所分配空间的起始位置的指针
分配失败:返回NULL指针
III注意事项
(1)malloc分配的内存块中的内容是未初始化的
(2)使用完malloc分配的内存之后,必须使用free函数进行释放,以免出现内存泄漏
(3)malloc返回的是void*类型的指针,使用时通常会进行类型转换
(4)内存泄漏的定义是已动态分配的内存未被及时释放,导致这部分内存无法再被程序或者其他进程再利用。这种情况如果逐渐积累的话会降低系统性能,甚至是导致系统崩溃
(5)如果传入的参数为0,malloc的行为是未定义的,取决于编译器
2.2free
free函数就是手动释放已动态分配的内存的函数
I传入的参数是所分配空间的起始位置的指针,该函数会进行隐式转换
II注意事项
(1)如果ptr是NULL,那free函数什么都不会做;
(2)如果ptr所指向的空间不是动态分配,那么free函数的行为是未定义的
(3)不要对未初始化的指针调用free函数。这样做是未定义行为,可能会导致程序崩溃。
3.示例:
malloc函数和free函数都在头文件<stdlib.h>中申明
3.calloc和realloc
3.1calloc
calloc的主要功能是在堆上分配一块指定大小的内存,并将这块内存中的每一位都初始化为零
I从左往右
传入的第一个参数是要分配的元素个数
第二个参数表示每个元素的大小,单位是字节
II返回值
分配成功:返回所分配空间的起始位置的地址
分配失败:返回NULL指针
III特点
(1)calloc函数用于在程序的动态存储区(堆)中分配内存
(2)与malloc函数不同,calloc函数在分配动态内存的同时会将所分配内存区域的所有字节都初始化为0
(3)calloc函数分配动态内存成功后会返回一个类型为void*的指针,程序员应按需进行类型转换
3.1.1示例:
3.2realloc
realloc
是 C 语言标准库中的一个函数,用于重新分配先前通过 malloc
、calloc
或 realloc
分配的内存块的大小
I 从左往右
传入的第一个参数是先前通过 malloc
、calloc
或 realloc
分配的内存块的起始位置的地址
第二个参数指的是想要将该内存块改为多大,单位是字节
II返回值
分配成功:返回新分配的内存块的指针
分配失败:返回空指针,并且原来内存块中的内容不变
III特点
(1)realloc允许程序在运行时根据需要调整内存块的大小
(2)情况0:新的内存块大小小于原内存块,则多余的部分会被截断,可能导致数据丢失
情况1:新的内存块大小大于原内存块,并且原内存块后面有足够的连续空间,就直接在原有内存之后直接追加空间,原来空间的数据不发⽣变化。
情况2:新的内存块大小大于原内存块,并且原内存块后面没有足够的连续空间,realloc
会在堆上另找一个合适大小的连续空间,将原内存块的内容复制到新位置,并释放原内存块。
图片来源:鹏哥c语言
(3)由于realloc可能会移动内存块的位置,因此要始终使用realloc返回的指针来访问内存块,而不是原来的指针
3.2.1示例:
IIII注意事项
(1)为了防止realloc分配内存失败返回空指针,应选择创建新指针来接受realloc的返回值,当该返回值不为空时,再赋值给先前的指针,便于内存释放
(2)下面两行代码都是在堆区开辟了10个字节的空间,并返回相应空间起始位置的指针
malloc(10);
realloc(NULL, 10);
4.常见的动态内存的错误
4.1对NULL指针的解引用操作
void test()
{
int * p = (int*)malloc(INT_MAX/4);
*p = 20;
free(p);
p = NULL;
}
(1)申请内存之后,未对返回值进行检验,如果为返回值为空,即p为NULL指针,则会导致以下问题:程序崩溃、数据损坏、安全漏洞等
4.2对动态开辟空间的越界访问
void test()
{
int *p = (int*)calloc(10, sizeof(int));
if (p == NULL)
{
perror("calloc");
return 1;
}
for (int i = 0; i < 40; ++i)//越界访问了
printf("%d ", p[i]);
free(p);
p = NULL;
}
(1) 越界访问会导致以下问题:程序崩溃、数据损坏等
4.3对非动态开辟内存进行free释放
void test()
{
int w = 13;
int* p = &w;
free(p);
p = NULL;
}
(1) 对非动态开辟内存进行free释放会导致以下问题:程序崩溃、数据损坏等
4.4使用free释放一块动态开辟内存的一部分
void test()
{
int *p = (int*)calloc(10, sizeof(int));
//.........
p++;
free(p);
p = NULL;
}
(1)使用free释放一块动态开辟内存的一部分会导致以下问题:程序崩溃、数据损坏、内存泄漏或安全漏洞等
4.5对同一块动态内存多次释放
void test()
{
int *p = (int*)calloc(10, sizeof(int));
//.........
free(p);
free(p);
}
(1)对同一块动态内存多次释放导致以下问题:程序崩溃、数据损坏等
4.6忘记释放不再使用的动态开辟的空间
忘记释放不再使用的动态开辟的空间会造成内存泄漏
(1)内存泄漏的定义是已动态分配的内存未被及时释放,导致这部分内存无法再被程序或者其他进程再利用。这种情况如果逐渐积累的话会降低系统性能,甚至是导致系统崩溃
5.动态内存经典笔试题
5.1
void GetMemory(char *p)
{
p = (char *)malloc(100);
}
void Test(void)
{
char *str = NULL;
GetMemory(str);
strcpy(str, "hello world");
printf(str);
}
(1)Getmemory使用的是传值传参 ,str未得到相应空间起始位置的指针
(2)未释放内存
改法1:使用传址传参
void GetMemory(char **p)//二级指针
{
*p = (char *)malloc(100);
}
void Test(void)
{
char *str = NULL;
GetMemory(&str);//取地址
strcpy(str, "hello world");
printf(str);
free(str);
str = NULL;
}
改法2:返回值
char * GetMemory()//返回动态开辟内存起始位置的指针
{
char* p = (char *)malloc(100);
return p;
}
void Test(void)
{
char *str = NULL;
str = GetMemory();//str用来接收
strcpy(str, "hello world");
printf(str);
free(str);
str = NULL;
}
5.2
char *GetMemory(void)
{
char p[] = "hello world";
return p;
}
void Test(void)
{
char *str = NULL;
str = GetMemory();
printf(str);
}
(1)当Getmemory调用结束之后,字符数组就会自动销毁,str就会变成一个野指针,导致出错
(2)未释放内存
5.3
void GetMemory(char **p, int num)
{
*p = (char *)malloc(num);
}
void Test(void)
{
char *str = NULL;
GetMemory(&str, 100);
strcpy(str, "hello");
printf(str);
}
(1)未释放内存
5.4
void Test(void)
{
char *str = (char *) malloc(100);
strcpy(str, "hello");
free(str);
if(str != NULL)
{
strcpy(str, "world");
printf(str);
}
}
(1)调用free函数之后,str不为空,但是没被置空,变成了野指针