声明:由于本人水平有限,文章中难免有不准确和错误之处本人也很想知道这些错误,恳请读者批评指正,大家一起努力,加油!
本章主要介绍动态内存相关的函数及使用
目录
为什么存在动态内存分配
我们已知的内存开辟方式
1.在栈空间上开辟40个字节的空间。
int a = 10;
2.在栈空间上开辟10个字节的连续空间。
char arr[10] = {0};
但上述内存开辟方式有两个缺点:
1.空间的开辟大小是固定的。
2.数组在声明的时候必须指定数组的长度,他所需要的内存在编译时分配。
但有时候我们需要的空间大小在程序运行的时候才能知道,那以上这两种方法就不能满足我们的需求,这时就只能试试动态内存分配了。
动态内存开辟
动态内存函数的介绍
malloc和free
作用:这个函数向内存申请一块连续可用的空间,并返回这块空间的指针
void* malloc (size_t size);
例子
#include<stdlib.h>
int main()
{
//开辟
int* p = (int*)malloc(10 * sizeof(int));//在堆上开辟10个整形的空间
//判断
if (p == NULL)
{
perror("main");
return 1;
}
//使用
int i = 0;
for (i = 0; i < 10; i++)
{
*(p + i) = i;//赋值
}
for (i = 0; i < 10; i++)
{
printf("%d ", p[i]);//打印
}
//释放
free(p);
//p置空
p = NULL;
return 0;
}
注意:
1.malloc和ferr一般成对使用,free你把开辟的空间还给操作系统,但free不会把p置为空指针,所以我们要手动把p置空,防止在以后的代码中出现非法访问。
2.free只能释放动态内存开辟的空间。
开辟失败的例子
int* p = (int*)malloc(100000000000 * sizeof(int));
//判断
if (p == NULL)
{
perror("main");
return 1;
}
calloc
作用:开辟一块连续的空间,并初始化为0。
void* calloc (size_t num, size_t size);
例子
#include<stdlib.h>
int main()
{
//开辟
int* p = (int*)calloc(10, sizeof(int));//在堆区上开辟10个整形的空间
//判断
if (p == NULL)
{
perror("main");
return 0;
}
//使用
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", p[i]);//calloc会初始化为0
}
//释放
free(p);
//置空
p = NULL;
return 0;
}
注意:malloc和callic的区别就是参数不同,另外calloc会初始化为0,其余全部相同。
realloc
函数参数
void* realloc (void* ptr, size_t size);
realloc如何调整空间?
第一种情况
如果堆空间后面的空间够用,我们就直接增加40个字节,并把起始地址返回去。
第二种情况
如果堆中后面的空间不够用了,realloc会在内存中重新找一块空间,把p所指向空间的数据拷贝下来,然后返回新开辟空间的地址,并ferr掉p所指向的空间。
例子
#include<stdlib.h>
int main()
{
//开辟
int* p = (int*)calloc(10, sizeof(int));//在堆区上开辟10个整形的空间
//判断
if (p == NULL)
{
perror("main");
return 0;
}
//使用
int i = 0;
for (i = 0; i < 10; i++)
{
*(p + i) = 5;
}
//需要p指向的空间更大,需要20个整形的空间
//增容
int* ptr = (int*)realloc(p,20* sizeof(int));
if (ptr != NULL)
{
p = ptr;//增容成功ptr赋给p
}
//释放
free(p);
//置空
p = NULL;
return 0;
}
总结:malloc,calloc用来开辟空间,ferr释放空间,realloc调整空间。
动态内存中常见的错误
对NULL指针的解引用操作
int main()
{
int* p = malloc(10000000000);
*p = 20; //如果p为NULL呢?
free(p);
return 0;
}
malloc在堆上开辟内存的时候可能会开辟失败返回NULL,这时候多NULL进行解引用操作肯定会出问题。解决办法就是malloc开辟完后多p指针进行判断。
对动态开辟空间的越界访问
#include<stdio.h>
int main()
{
int* p = (int*)malloc(10 * sizeof(int));
if (p == NULL)
{
return 1;
}
int i = 0;
for (i = 0; i < 40; i++)
{
*(p + i) = i;//越界访问,我们只开辟了10个整形的空间
}
free(p);
p = NULL;
}
使用free释放非动态开辟的空间
int main()
{
int arr[10] = { 0 };
int* p = arr;//arr是在栈上开辟的空间
free(p);
p = NULL;
return 0;
}
使用free释放动态内存中的一部分
int main()
{
int* p = (int*)malloc(10 * sizeof(int));
if (p == NULL)
{
return 1;
}
int i = 0;
for (i = 0; i < 5; i++)
{
*p++ = i;//改变p的指向
}
free(p);//只释放后半部分
p = NULL;
return 0;
}
对同一块动态开辟的空间多次释放
int main()
{
int* p = (int*)malloc(100);
if (p == NULL)
{
return 1;
}
//释放
free(p);
//。。。。。。
free(p);
p = NULL;
return 0;
}
动态开辟空间忘记释放(内存泄漏)
void tast()
{
int* p = (int*)malloc(10 * sizeof(int));
if (p == NULL)
{
return;
}
//使用
//........忘记释放,出了这个函数后再也找不到这块空间了
}
int main()
{
tast();
return 0;
}
动态开辟的空间,两种回收方式
1.free主动释放
2.程序结束的时候
经典的笔试题
笔试1
思考如下代码运行的结果:
void GetMemory(char* p)
{
p = (char*)malloc(100);
}
void test(void)
{
char* str = NULL;
GetMemory(str);
strcpy(str, "hello");
printf(str);
}
int main()
{
test();
return 0;
}
分析:
str本是一个指针变量,str传给getmenory是采用值传递的方式,此时的形参p是实参str的一份临时拷贝,函数内部在p这个地址处开辟100字节的空间,但是出了函数后p就会销毁,这时已经内存泄漏了。此时test函数内部的str还是NULL,把hello这个常量字符串拷贝到NULL处,什么也不会发生。
改正:
void GetMemory(char** p)
{
*p = (char*)malloc(100);
if (*p == NULL)
{
return;
}
}
void test(void)
{
char* str = NULL;
GetMemory(&str);
strcpy(str, "hello");
printf(str);
free(str);
str == NULL;
}
int main()
{
test();
return 0;
}
笔试2
char* GetMemory()
{
char p[] = "hello";
return p;
}
void test(void)
{
char* str = NULL;
str = GetMemory();
printf(str);
}
int main()
{
test();
return 0;
}
分析:GetMemory函数在返回的是p这个数组的首地址,但是p数组的空间在出函数的时候已经还给了操作系统,printf打印的时候已经算是非法访问了。这种情况问题为返回栈空间的地址。
笔试3
char* GetMemory(char**p,int num)
{
*p = (char*)malloc(num);
}
void test(void)
{
char* str = NULL;
GetMemory(&str, 100);
strcpy(str, "hello");
printf(str);
}
int main()
{
test();
return 0;
分析:GetMemory函数内部动态开辟的空间没有free还给操作系统,造成内存泄漏。
笔试4
void test(void)
{
char* str = (char*)malloc(100);
strcpy(str, "hello");
free(str);
if (str != NULL)
{
strcpy(str, "world");
printf(str);
}
}
int main()
{
test();
return 0;
}
分析:提前释放str所指向的空间,if语句虽然能进去,但是str所指向的空间已经还给了操作系统,再想把world放进去已经造成了非法访问。所以我们free释放后str一定要置空。
柔性数组
解释:C99中允许结构的最后一个成员是未知大小的数组,这就叫柔性数组成员
struct S
{
int a;
int arr[];
};
柔性数组的特点:
1,结构中柔性数组成员前至少有一个其他成员。
2,sizeof返回时不包含柔性数组的内存。
3,包含柔性数组的结构,用malloc进行动态内存分配,并且分配的大小应大于结构的大小,已适应柔性数组预期的大小。
使用:
struct S
{
int a;
int arr[];
};
int main()
{
struct S* ps = (struct S*)malloc(sizeof(struct S) + 10 * sizeof(int));
if (ps == NULL)
{
perror("main");
return 0;
}
int i = 0;
ps->a = 10;
for (i = 0; i < 10; i++)
{
ps->arr[i] = i;
}
//增容
struct S* ptr = (struct S*)realloc(ps, sizeof(struct S) + 20 * sizeof(int));
if (ptr != NULL)
{
ps = ptr;
}
//释放
free(ps);
ps = NULL;
return 0;
}
动态内存管理的相关内容到这里就结束啦,感谢您的阅读。