网站后台管理系统登陆,网站建设 业务培训,牧童蝉网站建设,网站建设基本步骤包括哪些一、为什么要有动态内存管理
从我们平常的学习经历来看#xff0c;所开辟的数组一般都为固定长度大小的数组#xff1b;但从很多现实需求来看需要我们开辟一个长度“可变”的数组#xff0c;即这个数组的大小不能在建立数组时就指定#xff0c;需要根据某个变量作为标准。…一、为什么要有动态内存管理
从我们平常的学习经历来看所开辟的数组一般都为固定长度大小的数组但从很多现实需求来看需要我们开辟一个长度“可变”的数组即这个数组的大小不能在建立数组时就指定需要根据某个变量作为标准。
如比较常见的就是一些编程题中输入一个变量n来作为数组的长度等PS虽C99支持变长数组但我们这里主要讨论数组共性的标准可能还有一种情况就是在往数组中放数据时由于一开始空间大小指定不合适出现了空间不足的情况此时就需要进行“扩容”操作。
由此一来动态内存管理应运而生。
二、动态内存函数
1、malloc和free
1malloc函数介绍
函数的声明为
void* malloc (size_t size);函数的作用 这个函数向内存申请一块连续可用的空间并返回指向这块空间的指针通过相应类型的指针变量接收返回的指针后即可通过该指针变量使用已开辟的内存空间函数的特性 如果开辟成功则返回一个指向开辟好空间的指针。 如果开辟失败则返回一个NULL指针因此malloc的返回值一定要做检查。 返回值的类型是void* 因为malloc函数并不知道开辟空间的类型具体在使用的时候使用者自己来决定。 如果参数 size为0malloc的行为是标准是未定义的取决于编译器
使用示例 int n 0;scanf(%d, n);int* a (int*)malloc(sizeof(int) * n);//开辟大小为n个整型的空间if (a ! NULL) //判断空间是否开辟成功{a[0] 1; //对空间进行使用//...}可以看到我们在使用时需要通过强制类型转换“告诉”编译器我们开辟空间的类型返回指针的类型PS其实空间并没有所谓的“类型”的概念仅是通过指针的不同类型而有不同的看待空间的视角
2free函数介绍
函数声明为
void free (void* ptr);函数的作用 释放动态开辟的内存函数的特性 如果参数 ptr 指向的空间不是动态开辟的那free函数的行为是未定义的。 如果是动态开辟的则该函数会将ptr指向空间的使用权进行归还其中有两部分大部分归还给操作系统真正释放另一部分未归还操作系统的已释放内存被恢复到空闲池free pool中并可再次进行分配 如果参数 ptr 是NULL指针则函数什么事都不做。
使用示例 int n 0;scanf(%d, n);int* a (int*)malloc(sizeof(int) * n);//开辟大小为n个整型的空间if (a ! NULL) //判断空间是否开辟成功{a[0] 1; //对空间进行使用//...}free(a); //释放动态开辟的内存使用注意事项 从函数的特性中我们可以看出在free一块动态开辟的内存后那块空间按理来说已经不能再访问了虽然的确可以故意去访问当访问到的是空闲池中的内存时可能不会有什么大问题但若访问到了操作系统的内存程序就会崩溃又因为free仅负责将动态开辟的空间的使用权进行归还并不对用于接收这块空间起始地址的指针进行处理示例中a再free之后仍指向那块空间故为了内存访问的安全在free之后需对相应的指针进行置空操作。示例中最后还需加上a NULL
2、calloc
1 函数的声明为
void* calloc (size_t num, size_t size);2函数的作用 作用和malloc一样唯一不同的是它可以将动态开辟好的空间进行初始化即将开辟好的num个空间的值都初始化为0。 3函数的特性 同malloc但多了一个初始化功能。
使用示例 int n 0;scanf(%d, n);int* a (int*)calloc(n,sizeof(int));//开辟大小为n个整型的空间3、realloc
1 函数的声明为:
void* realloc (void* ptr, size_t size);2函数的作用 对动态开辟内存大小进行调整一般用于给已开辟的空间进行扩容。
3函数的特性 参数ptr是要调整的内存地址size是调整之后的新大小一般即为新空间的大小为原空间大小需要扩展的空间的大小。
若参数size为0或空间开辟失败时函数返回NULL。
若参数ptr为NULL该函数等价于malloc如
int*p NULL;
p realloc(ptr, 1000);空间开辟成功会分为两种情况 原开辟空间后有足够空间可以扩容那么函数直接在原有内存之后直接追加空间原来空间的数据不发生变化返回旧的起始地址ptr的值 原开辟空间空间无法满足扩容需求函数会先在堆空间上另找一个新的合适大小的连续空间来使用接着把原空间的数据拷贝至新空间前面的位置并把原空间释放最后返回一个新空间的内存地址
从特性中我们得到使用时的重要一点 我们在对原空间进行扩容时需用一个同类型的新指针来接收扩容之后返回的指针以防分配失败而造成原有数据的丢失如
int *ptr (int*)malloc(100);
ptr (int*)realloc(ptr, 1000);//如上代码中若realloc分配失败放回空指针ptr所指向的原空间数据丢失
//正确应写为int *ptr (int*)malloc(100);
int *tmp (int*)realloc(ptr, 1000);
if(tmp ! NULL)
{ptr tmp;
}
补充一点 realloc函数一般仅用于对内存进行扩展也就是扩容很少会用于缩容并且缩容会存在一些问题并且可能不能达到我们预期的效果。如下通过VS2022下的调试说明一下 用于调试的代码
int main()
{int* p (int*)malloc(10 * sizeof(int));p[5] 1;p (int*)realloc(p, 5*sizeof(int));p[5] 2;return 0;
}分配10个整型的空间后将第6个整型空间的值改为1没问题接下来将p的空间缩减为5个整型空间 可以看到后5个整型空间好像确实是回收给系统了那么接下来执行a[5] 2;应为越界访问的行为系统按理来说会报错但实际是 仍完成了对原空间第6个整型空间的值的更改这就与我们的预期效果大相径庭了。故一般不用realloc来进行缩容。
三、常见动态内存错误
1、对NULL指针的解引用操作
若对动态开辟返回的指针不做检查就可能发生如
int *p (int *)malloc(INT_MAX/4);
*p 20; //如果p的值是NULL就会有问题
free(p);2、对动态开辟的内存空间进行了越界访问
和数组越界访问的问题类型
int i 0;
int *p (int *)malloc(10*sizeof(int));
if(NULL p)
{exit(-1);
}
for(i0; i10; i)
{*(pi) i; //当i是10的时候越界访问
}
free(p);3、对非动态开辟内存使用free释放
int a 10;
int *p a;
free(p);如上代码运行后系统崩溃
4、 使用free释放一块动态开辟内存的一部分
int *p (int *)malloc(100);
p; //p不再指向动态内存的起始位置
free(p); 如上代码运行后系统崩溃
5、对同一块动态内存多次释放
int *p (int *)malloc(100);
free(p);
free(p);//重复释放如上代码运行后系统崩溃
6、使用完动态开辟的内存后忘记释放
若使用完动态开辟的空间后没有通过free函数进行内存释放就会造成恐怖的内存泄露问题体现在我们的程序中可能没什么大问题程序会结束或关闭顺带着内存就会回收但若体现在一些长时间不停机服务器中就会造成服务器越用越卡直至死机的严重后果。
四、关于内存管理的经典题目
了解完动态内存管理的基础知识后可以看看一些关于内存管理的经典题目来趁热打铁请看 1、运行Test 函数的结果是
void GetMemory(char *p)
{p (char *)malloc(100);
}
void Test(void)
{char *str NULL;GetMemory(str);strcpy(str, hello world);printf(str);
}结果 程序会崩溃。 原因 解引用了空指针。在GetMemory函数中动态分配内存后返回的指针赋值给了p但由于形参对实参的临时拷贝故改变了p并不影响str故在GetMemory函数调用结束后str的值仍为NULL在进行strcpy时发生了错误。
2、运行Test 函数的结果是
char *GetMemory(void)
{char p[] hello world;return p;
}
void Test(void)
{char *str NULL;str GetMemory();printf(str);
}结果 程序会打印“烫烫烫烫烫烫…”的乱码 原因 返回栈空间地址问题。GetMemory中p为局部变量其在出了GetMemory函数作用域后会销毁销毁后其原指向的空间的值就为随机值也就是说用str接收的指针所指向的空间随机值故再以打印字符串的方式去打印str的内容时就会乱码。
3、运行Test 函数的结果是
void GetMemory(char **p, int num)
{*p (char *)malloc(num);
}
void Test(void)
{char *str NULL;GetMemory(str, 100);strcpy(str, hello);printf(str);
}结果 正常输出hello 原因 其实这才是相对于题目1的正确写法由于实参本身就是一个指针故需要一个二级指针来实现改变形参而改变实参。
4、运行Test 函数的结果是
void Test(void)
{char *str (char *) malloc(100);strcpy(str, hello);free(str);if(str ! NULL){strcpy(str, world);printf(str);}
}结果 看似正常输出了world 原因 在介绍free函数时有说到free仅将动态开辟的空间的使用权还给系统但并不改变用于“接收”指向这块空间的指针变量的值。也就是说代码中的str在free后的值仍未原空间的起始地址不为空但此时对原空间已没有使用权故在进行strcpy时本质上已经是非法访问了内存空间只是可能访问到的是空闲池而程序没有崩溃。
五、C/C内存区域划分
C/C内存区域主要划分为如下几个区域 1、栈区stack在执行函数时函数内局部变量的存储单元都可以在栈上创建函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中效率很高但是分配的内存容量有限。 栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。
2、 堆区heap一般由程序员申请分配与释放 若程序员不释放程序结束时可能由操作系统回收 。
3、数据段静态区static存放全局变量、静态数据。程序结束后由系统释放。
4、代码段存放函数体类成员函数和全局函数的二进制代码
六、拓展柔性数组
1、定义
1概念C99 中结构中的最后一个元素允许是未知大小的数组该数组就称为柔性数组成员 2定义方式
typedef struct st_type
{int i;int a[0];//柔性数组成员
}type_a;或
typedef struct st_type
{int i;int a[];//柔性数组成员
}type_a;2、柔性数组的特点
其实包含柔性数组成员的结构体的特点主要有如下三点 1结构中的柔性数组成员前面必须至少一个其他成员 2sizeof 返回的这种结构大小不包括柔性数组的内存 3包含柔性数组成员的结构用malloc函数进行内存的动态分配并且分配的内存应该大于结构的大小以适应柔性数组的预期大小
3、柔性数组的使用及优势
1柔性数组的使用 柔性数组的使用主要在于对需要开辟的空间大小的把握空间正确开辟后和正常作为结构体成员的数组一样使用即可。下面是使用示例请看
type_a *p (type_a*)malloc(sizeof(type_a)10*sizeof(int));
//这里的柔性数组成员相当于获得了10个连续的整型空间//和正常数组一样使用即可
for(int i0; i10; i)
{p-a[i] i;
}free(p);2与指针成员相比的优势 如上的结构体type_a 也可设计为
typedef struct st_type
{int i;int* p_a;
}type_a;这样在分配和释放空间时就会相对麻烦一些
type_a *p (type_a *)malloc(sizeof(type_a));
//先为整个结构体分配空间p-p_a (int *)malloc(p-10 *sizeof(int));
//才能为里面的指针成员p分配空间for(int i0; i10; i)
{p-p_a[i] i;
}//要先释放指针成员的空间
free(p-p_a);
p-p_a NULL;
//再释放整个结构体的空间
free(p);
p NULL;相比之下我们可以得到柔性数组成员的两个优势 1方便内存释放 有柔性数组成员的结构体在释放空间时仅需将为整个结构体分配的内存释放即可而有指针成员的结构体在释放空间时需先释放指针为指针成员开辟的空间才能释放为整个结构体开的空间否则就会造成内存泄漏。 此时若是我们自己写的代码可能知道要先释放结构体指针成员的空间但如果写的代码是给别人用时用户可能就想着释放结构体就行而不再会去在意结构体里有什么。所以如果我们把结构体的内存以及其成员要的内存一次性分配好了并返回给用户一个结构体指针用户做一次free就可以把所有的内存也给释放掉降低了内存泄漏的风险。 2一定程度上提高了内存访问速度 柔性数组成员的地址空间相对于整个结构体成员的空间地址是连续的而指针成员则是碎片化的如下示意图
柔性数组成员 指针成员 连续的内存有益于提高访问速度。
本章完。
看完觉得有觉得帮助的话不妨点赞收藏鼓励一下有疑问或看不懂的地方或有可优化的部分还恳请朋友们留个评论多多指点谢谢朋友们