C语言——动态内存管理
目录
一为什么要动态内存管理
二内存函数
1malloc
2free
3calloc
4realloc
三创建错误
1没有判断直接使用
2对开辟的空间越界访问
3对非开辟的内存进行释放
4只释放开辟内存的一部分
5对同一块开辟内存多次释放
6忘记释放开辟的内存(内存泄漏)
四常见笔试题
题1
题2
题3
五C/C++程序的内存开辟
六对通讯录进行改造
七柔性数组
1特点
2使用
一为什么要动态内存管理
前面已经掌握了开辟内存的方式:
int val = 20;//在栈空间上开辟四个字节
但这种开辟内存的方式有两个特点:
1. 空间开辟大小是固定的;
2. 数组在声明的时候,必须指定数组的长度,它所需要的内存在编译时分配。
如果一个数组在使用时发现不够用(前面通讯录信息存满了还要存信息),我们就要进行来学习动态内存开辟内存来满足需求~
二内存函数
1malloc
a 这个函数向内存(堆区)申请一块连续可用的空间,并返回指向这块空间的指针;
b 如果开辟成功,则返回一个指向开辟好空间的指针;
c 如果开辟失败,则返回一个NULL指针;因此malloc的返回值一定要做检查;
d 返回值的类型是 void* ,因为malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定设置;
e 如果参数 size 为0,malloc的行为是标准是未定义的,现象取决于编译器。
2free
自己申请的内存一定要记得释放,所以有了free函数:
free函数专门用来释放动态开辟的内存;
如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的(报错);
如果参数 ptr 是NULL指针,则函数什么事都不做;所以使用free后要把指针置NULL(不然就是野指针)
使用:
#include<stdio.h>
int main()
{
int *ptr = (int*)malloc(10 * sizeof(int));
if (NULL == NULL)//判断ptr指针是否为空
{
perror("malloc error");
return 1;
}
//使用...
free(ptr);//释放ptr所指向的动态内存
ptr = NULL;//一定要记得置空
return 0;
}
3calloc
calloc函数与malloc不同的一点是:开的空间有帮你进行初始化为0; 参数方面:比malloc更细(不用你去算总共开辟空间的字节数):开num个大小为size的空间
#include<stdio.h>
#include<stdlib.h>
int main()
{
int* ptr1 = (int*)calloc(5, sizeof(int));
int* ptr2 = (int*)malloc(5*sizeof(int));
if (ptr1 == NULL || ptr2 == NULL)//判断ptr指针是否为空
{
perror("malloc error");
return 1;
}
printf("ptr1=");
for (int i = 0; i < 5; i++)
{
printf("%d ", ptr1[i]);
}
printf("\nptr2=");
for (int i = 0; i < 5; i++)
{
printf("%d ", ptr2[i]);
}
free(ptr1);
ptr1 = NULL;
free(ptr2);
ptr2 = NULL;
return 0;
}
4realloc
realloc的出现让内存管理更加灵活!
ptr传入要调整的内存首元素地址,size是调整后的新大小;
返回值为调整后的新内存的起始地址;
realloc在调整内存空间是会存在以下两种情况:
a.原来空间后面有足够大的空间可以用来开辟(通常情况)
b.原来空间后面没有足够大的空间,需要realloc自己重新开辟新空间;
此时realloc:
1.开辟新空间后,将旧的数据拷贝过来;
2.释放旧空间;
3.返回新空间的起始地址
#include <stdio.h>
#include <stdlib.h>
int main()
{
int* p = (int*)malloc(100*sizeof(int));
if (p == NULL)
{
perror("malloc error");
return 1;
}
//使用...
//空间不够要进行扩容
//int* p = (int*)realloc(p, 200 * sizeof(int));//这种不保险,有可能扩容失败!
int* p1 = (int*)realloc(p, 20000*sizeof(int));
if (p1 != NULL)
{
p = p1;
}
//使用...
free(p);
p = NULL;
return 0;
}
三创建错误
1没有判断直接使用
2对开辟的空间越界访问
3对非开辟的内存进行释放
4只释放开辟内存的一部分
5对同一块开辟内存多次释放
6忘记释放开辟的内存(内存泄漏)
#include <stdio.h>
void test()
{
int* p = (int*)malloc(100);
if (NULL != p)
{
*p = 20;
}
}
int main()
{
test();
while (1);//程序不退出
}
四常见笔试题
指出以下代码存在的问题
题1
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void GetMemory(char* p)
{
p = (char*)malloc(100);
}
void Test(void)
{
char* str = NULL;
GetMemory(str);
strcpy(str, "hello world");
printf(str);
}
int main()
{
Test();
return 0;
}
1.调用GetMeonry()时传的是实参,函数结束时p空间销毁(申请的空间还在,但是此时不知道地址),此时str还是NULL,strcpy对NULL解引用造成非法访问;
2.申请空间没有用free释放,会造成内存泄漏
题2
#include<stdio.h>
#include<stdlib.h>
char* GetMemory(void)
{
char p[] = "hello world";
return p;
}
void Test(void)
{
char* str = NULL;
str = GetMemory();
printf(str);
}
int main()
{
Test();
return 0;
}
str接收p指针时,此时p指针指向的空间已经被销毁了,打印printf属于非法访问
题3
#include<stdio.h>
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;
}
1进行malloc申请空间时没有对str进行判断;
2free释放str申请的空间后,还对str进行操作,属于非法访问(free后要将str置NULL)
五C/C++程序的内存开辟
C/C++程序内存分配的几个区域:
栈区(stack):
函数栈帧的创建与销毁都是在栈区进行的;
栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等;
堆区(heap):一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收;
数据段(静态区)存放全局变量、静态数据。程序结束后由系统释放;代码段:存放函数体(类成员函数和全局函数)的二进制代码。
六对通讯录进行改造
在上篇文章的通讯录代码进行修改:
将通讯录起始空间为2,随着信息的增加,空间满后将进行扩容(每次扩容3)
//Contact.h
typedef struct Contact
{
int sz;
//Mess message[Max_Size];
Mess* message;
int capacity;
}Contact;
//Contact.c
/*if (con->sz == Max_Size)
{
printf("Contact Is Full\n");
return;
}*/
if (con->sz == con->capacity)
{
//扩容
Mess* ptr = realloc(con->message, (con->capacity + Default_Size)*sizeof(Mess));
if (ptr != NULL)
{
con->message = ptr;
con->capacity += Default_Size;
printf("扩容成功\n");
}
else
{
perror("realloc error");
return;
}
}
void Destory(Contact* con)
{
free(con->message);
con->message = NULL;
con->capacity = 0;
con->sz = 0;
}
七柔性数组
也许你听说过柔性数组(flexible array)这个概念,但是却不对它有很多了解;
柔性数组:结构体中最后一个成员允许是未知大小的数组
typedef struct st_type
{
int i;
int a[0];//柔性数组成员
int a[];//或者写成这种
}type_a;
1特点
a 结构中的柔性数组成员前面必须至少一个其他成员;
sizeof 返回的这种结构大小不包括柔性数组的内存;
包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小(给到柔性数组储存)
2使用
实现:在结构体中,用数组来储存数据(数据大小未知)
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
//使用柔性数组
int main()
{
typedef struct st_type
{
int t;
int a[0];//柔性数组成员
//int a[];另外写法
}type_a;
type_a* p = (type_a*)malloc(sizeof(type_a) + 5 * sizeof(int));
if (p == NULL)
{
perror("malloc error");
return 1;
}
p->t = 100;
int i = 0;
for (i = 0; i < 5; i++)
{
p->a[i] = i;
}
for (int i = 0; i < 5; i++)
{
printf("%d ", p->a[i]);
}
//空间不够要进行扩容
type_a* pa = (type_a*)realloc(p, sizeof(type_a) * 10 * sizeof(int));
if (pa == NULL)
{
perror("realloc error");
return 2;
}
p = pa;
//使用...
free(p);
p = NULL;
return 0;
}
//不使用柔性数组同样能实现以上功能
int main()
{
typedef struct st_type
{
int t;
int* a;
}type_a;
type_a* p = (type_a*)malloc(sizeof(type_a));
if (p == NULL)
{
perror("malloc error");
return 1;
}
p->t = 100;
p->a = (int*)malloc(sizeof(int) * 5);//要malloc两次,麻烦
if (p -> a == NULL)
{
perror("malloc error");
return 1;
}
int i = 0;
for (i = 0; i < 5; i++)
{
p->a[i] = i;
}
for (int i = 0; i < 5; i++)
{
printf("%d ", p->a[i]);
}
//空间不够要进行扩容
int* pb = (int*)realloc(p->a, 10 * sizeof(int));
if (pb == NULL)
{
perror("realloc error");
return 2;
}
p->a = pb;
//使用...
//要按顺序释放,麻烦
free(p->a);
p->a = NULL;
free(p);
p = NULL;
return 0;
}
相较于使用柔性数组而言:
好处1:可对数组直接进行访问使用
而另一种做法要先malloc存放结构体,再malloc存放数组,需要两次才能访问数组,麻烦~;
好处2:内存释放只需1次,简单
而另一种做法要先把数组空间释放,再释放结构体空间(注意顺序还不能颠倒!),麻烦~
如果还想了解关于结构体的内容,欢迎点击: C语言结构体里的成员数组和指针
以上便是全部内容,有问题欢迎在评论区指出,感谢观看!