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;
}
————————————————
                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/2302_78794424/article/details/145018469

版权声明:
作者:SE_Gai
链接:https://www.cnesa.cn/2965.html
来源:CNESA
文章版权归作者所有,未经允许请勿转载。

THE END
分享
二维码
打赏
海报
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; } //使……
<<上一篇
下一篇>>