文章目录
一、动态内存函数
要介绍动态内存函数,我们就要知道动态内存函数开辟的空间在内存的什么位置。
如下图所示:
动态内存函数开辟的空间在堆区,该区域的空间,只有free函数和程序结束才会释放。
1. malloc
函数的声明:void* malloc (size_t size);包含在<stdlib.h>头文件中。
调用该函数可以向内存中申请大小为size个字节的连续空间,并返回该空间的起始地址。
注意:
- 如果malloc开辟空间成功,则返回成功开辟好空间的起始地址
- 如果malloc开辟空间失败,则返回NULL
- 不要开辟大小为0的空间,这是标准未定义的。
因此,malloc开辟空间后,我们要检查返回值。
#include <stdio.h>
#include <stdlib.h>
int main()
{
int* pa = (int*)malloc(sizeof(int) * 10);//开辟大小为40个字节的空间
//检查是否空间开辟失败
if (pa == NULL)
{
exit(-1);
}
free(pa);
pa = NULL;
return 0;
}
2. calloc
函数的声明:void* calloc (size_t num, size_t size);包含在<stdlib.h>头文件中。
调用该函数可以向内存中申请num个大小是size个字节的连续空间,再将空间每一个字节初始化为0,并返回该空间的起始地址。
calloc与malloc的唯一区别在于,calloc会将空间每一个字节初始化为0。
如下所示:
#include <stdio.h>
#include <stdlib.h>
int main()
{
int* arr1 = (int*)malloc(sizeof(int) * 10);
if (arr1 == NULL)
{
exit(-1);
}
for (int i = 0; i < 10; i++)
{
printf("%d ", arr1[i]);
}
printf("\n");
int* arr2 = (int*)calloc(sizeof(int), 10);
if (arr2 == NULL)
{
exit(-1);
}
for (int i = 0; i < 10; i++)
{
printf("%d ", arr2[i]);
}
free(arr1);
free(arr2);
arr1 = NULL;
arr2 = NULL;
return 0;
}
3. realloc
函数的声明:void* realloc (void* ptr, size_t size);包含在<stdlib.h>头文件中。
调用该函数可以调整由malloc和calloc开辟的空间大小,调整为size个字节大小的空间。
但要注意,其调整空间大小的方式有两种,原地扩容和异地扩容。
-
原地扩容,原地址空间后空间大小充足,在原地址空间直接扩容。
-
异地扩容,原地址空间后空间大小不够,在堆区另找一片充足的空间使用。
-
如果调整的大小过大,realloc会返回NULL。
//异地扩容
int main()
{
int* pa = (int*)malloc(sizeof(int) * 10);
if (pa == NULL)
{
exit(-1);
}
printf("%p\n", pa);
int* tmp = (int*)realloc(pa, sizeof(int) * 20);
if (tmp != NULL)
{
pa = tmp;
printf("%p\n", pa);
}
free(pa);
pa = NULL;
return 0;
}
//原地扩容
int main()
{
char* pa = (char*)malloc(sizeof(char) * 10);
if (pa == NULL)
{
exit(-1);
}
printf("%p\n", pa);
char* tmp = (char*)realloc(pa, sizeof(char) * 20);
if (tmp != NULL)
{
pa = tmp;
printf("%p\n", pa);
}
free(pa);
pa = NULL;
return 0;
}
4. free
函数的声明:void free (void* ptr);包含在<stdlib.h>头文件中。
调用该函数可以释放由malloc和calloc开辟的空间,指针变量ptr本身内容不变,需要程序员本身置空。
- 如果ptr是NULL,则函数什么事都不会做
- 如果ptr并不是malloc和calloc开辟的空间,那free函数的行为未定义
如下:
int main()
{
int* pa = (int*)malloc(sizeof(int) * 10);
if (pa == NULL)
{
exit(-1);
}
free(pa);//释放pa所指向的空间
pa = NULL;//pa置NULL,防止野指针
return 0;
}
二、常见的错误
1.malloc或calloc开辟的空间未检查
有时开辟空间过大,而未检查时,会发生对NULL的解引用
int main()
{
int* pa = (int*)malloc(sizeof(int) * INT_MAX);
*pa = 10;
free(pa);
pa = NULL;
return 0;
}
2.越界访问
访问超出开辟空间大小
int main()
{
int* pa = (int*)malloc(sizeof(int) * 10);
if (pa == NULL)
{
exit(-1);
}
for (int i = 0; i < 11; i++)
{
pa[i] = 0;
}
free(pa);
pa = NULL;
return 0;
}
3.对非malloc和calloc开辟的空间,用free释放
free函数只能用了释放动态内存空间
int main()
{
int arr[10] = { 0 };
free(arr);
return 0;
}
4.对同一块动态内存多次释放
动态内存空间释放一次即可
int main()
{
int* pa = (int*)malloc(sizeof(int) * 10);
if (pa == NULL)
{
exit(-1);
}
free(pa);
free(pa);
pa = NULL;
return 0;
}
5.用free释放动态内存的一部分
开辟的动态内存空间实际大小要大于我们申请的大小,那多出的一部分空间要记录我们这次申请空间的信息,free函数就可以根据这一部分信息来释放我们申请的空间大小。如果我们传递的指针并未指向空间首地址,那么free就找不到信息来释放空间。
int main()
{
int* pa = (int*)malloc(sizeof(int) * 10);
if (pa == NULL)
{
exit(-1);
}
pa++;
free(pa);
pa = NULL;
return 0;
}
三、通讯录(动态版本改写)
静态版本通讯录
相对于静态版本而言,动态版本改变并不多也不难,下面就是要改变的部分。
- 结构
//动态版本
typedef struct Contact
{
PeoInfo* data;
int sz;//记录此时已用的大小
int capacity;//记录通讯录的大小
}Contact;
2.初始化InitContact
#define INITSIZE 10
//初始化通讯录
void InitContact(Contact* pc)
{
pc->sz = 0;
pc->capacity = INITSIZE;
pc->data = (PeoInfo*)malloc(sizeof(PeoInfo) * INITSIZE);
if (pc->data == NULL)
{
perror("malloc");
return;
}
}
- 扩容AddCapacity
bool AddCapacity(Contact* pc)
{
PeoInfo* tmp = realloc(pc->data, sizeof(PeoInfo) * (pc->capacity) * 2);
if (tmp == NULL)
{
perror("realloc");
return false;
}
pc->data = tmp;
pc->capacity *= 2;
return true;
}
//添加联系人
void AddContact(Contact* pc)
{
assert(pc);
if (pc->sz == pc->capacity)
{
if (AddCapacity(pc) == true)
{
printf("扩容成功\n");
}
else
{
printf("扩容失败\n");
return;
}
}
printf("请输入联系人名字:>");
scanf("%s", pc->data[pc->sz].name);
printf("请输入联系人性别:>");
scanf("%s", pc->data[pc->sz].sex);
printf("请输入联系人年龄:>");
scanf("%d", &(pc->data[pc->sz].age));
printf("请输入联系人电话:>");
scanf("%s", pc->data[pc->sz].tele);
printf("请输入联系人地址:>");
scanf("%s", pc->data[pc->sz].adder);
pc->sz += 1;
}
- 退出ExitContact
//退出通讯录
void ExitContact(Contact* pc)
{
free(pc->data);
pc->data = NULL;
pc->sz = 0;
pc->capacity = 0;
}
总结
以上就是我对于动态内存管理的了解和应用。