尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

深挖C语言:动态内存管理

深挖C语言:动态内存管理 1.为什么要有动态内存分配我们已经掌握的开辟方式int val 20;// 在栈空间上开辟四个字节 char arr[10] { 0 };//在栈空间上开辟10个连续的空间上述开辟空间由两个特点1. 空间开辟的大小固定。2. 数组在声明的时候必须指定数组长度数组空间一旦确定大小就不能调整。C语言引入了动态内存开辟让程序员自己可以申请和释放空间。2. malloc 和 free 函数2.1 malloc函数#includestdlib.h void* malloc( size_t size );功能向内存的堆区申请一块连续可用的空间并返回指向这块空间的起始地址。参数size要分配的内存块的字节数。返回值开辟成功返回这块空间的起始地址。开辟失败返回一个NULL指针因此一定要检查malloc的返回值。注1. 返回值的类型是 void* 具体在使用的时候需要我们强转为我吗需要的类型。2. 如果参数 size 为 0 malloc 的行为是标准未定义的取决于编译器。2.2 free函数#includestdlib.h void free( void* ptr );功能释放先前动态分配函数malloc、calloc、realloc申请的内存空间。参数ptr指向要释放内存的指针。注1.如果参数 ptr 指向的不是动态开辟的那 free 函数的行为是未定义的。2.如果参数 ptr 是NULL指针则函数不运行。代码演示#includestdio.h #includestdlib.h int main() { int num; scanf(%d,num); int arr[num]; int* ptr (int*)malloc(num * sizeof(int)); if(ptr ! NULL) { for(int i 0;i num;i) { *(ptr i) 0; }else { perror(malloc); retrun 1; } free(ptr); ptr NULL; return 0; }3.calloc 和 realloc 函数3.1 calloc函数#includestdlib.h void* calloc( size_t num , size_t size );功能将 num 个大小为 size 的元素开辟一块空间并且把空间的每个字节初始化为 0.与malloc函数的区别在于会把每个字节初始化为0代码示例#includestdio.h #includestdlib.h int main() { int* p (int*)calloc(10,sizeof(int)); if(p ! NULL) { for(int i 0;i 10;i) { printf(%d ,*(p i); } free(p); p NULL; return 0; }所以如果我们对申请的内存空间的内容要求初始化那么可以很方便的使用calloc函数来完成任务。3.2 realloc函数#includestdlib.h void* realloc( void* ptr , size_t size );功能重新调整之前分配的内存块大小它可以在不丢失原有数据的情况下扩大或缩小动态分配的内存块。参数ptr指向要调整的内存空间的起始地址如果 ptr 为NULLrealloc函数的功能类似于malloc函数。size调整后的内存块大小单位字节。返回值成功返回新内存块的起始地址可能与原地址不同。失败返回NULL原内存块保持不变。注意事项realloc在调整内存空间大小的时候存在两种情况情况1原有空间后面又足够大的空间要扩展的空间就直接在原来内存后面追加空间原来空间的数据不发生变化最终返回的地址还是旧地址。情况2原有空间之后没有太多空间会在内存堆区寻找新的满足要求的空间返回新的起始地址事件过程如下(1)寻找新的空间(2)将旧空间的数据拷贝到新空间保证数据不丢失(3)释放旧空间返回新空间的起始地址代码演示#includestdio.h #includestdlib.h int main() { int* ptr (int*)malloc(100); if(ptr ! NULL) { *(ptr i) i; }else{ perror(malloc); retrun 1; } //代码1直接放到原指针中 ptr (int*)realloc(ptr,1000); //代码2先放到其他指针中进行判断 int* p realloc(ptr,1000); if( p ! NULL ) { ptr p; p NULL; } free(ptr); ptr NULL; return 0; }4.常见的动态内存的错误4.1 对NULL指针的解引用操作void test() { int* p (int*)malloc(INT_MAX/4); *p 20;// 如果指针p是NULL就会报错 free(p); p NULL; }4.2 对动态开辟空间的越界访问void test() { int* p (int*)malloc(10 * sizeof(int)); if(p NULL) { return 1; } for(int i 0;i 10;i) { *(p i) i;//当i为10时越界访问 } free(p); p NULL; }4.3 对非动态开辟内存使用free释放空间void test() { int a 10; int* p a; free(p);// 报错 }4.4 使用free释放一块动态开辟内存的一部分void test() { int* p (int*)malloc(100); p; free(p);// p不在指向动态内存的起始空间空间未完全释放。 }4.5 对同一块动态内存多次释放void test() { int* p (int*)malloc(10); if(p NULL) { return 1; } free(p); free(p);//重复释放 }4.6 动态开辟内存忘记释放内存泄漏void test() { int* p (int*)malloc(100); if(p NULL) { rturn 1; }else { *p 20; } } int main() { test(); return 0; }动态开辟内存一定要释放并且正确释放。5.动态内存管理经典笔试题分析5.1 题目1#include stdio.h #include stdlib.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; }这里str为传值调用str并没有接收到开辟空间的起始地址依旧为 NULL。5.2 题目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(%s\n,str); } int main() { Test(); return 0; }问题p是函数里的临时数组函数一结束就被系统回收了把它的地址返回去外面拿到的地址已经无效。改法用 malloc 分配内存并返回或者直接返回字符串常量 hello world。5.3 题目3#include stdio.h #include stdlib.h #includestring.h void GetMemory(char** p, int num) { *p (char*)malloc(num); } void Test(void) { char* str NULL; GetMemory(str, 100); strcpy(str, hello); printf(%s\n,str); } int main() { Test(); return 0; }问题没检查 malloc 是否成功、没用 free释放空间。5.4 题目4#include stdio.h #include stdlib.h void Test(void) { char *str (char *) malloc(100); strcpy(str, hello); free(str); if(str ! NULL) { strcpy(str, world); printf(%s\n,str); } } int main() { Test(); return 0; }问题free(str) 之后没有把 str 置为 NULL导致 if( str ! NULL) 判断为真接着对已释放的内存执行 strcpy 和 printf这是 释放后使用 的未定义行为。6.柔性数组在C99中结构中的最后一个元素允许是未知大小的数组这就叫做柔性数组。struct Example { int len; // 其他成员 int data[]; // 柔性数组不占结构体本身的空间 };柔性数组特点1. 前面必须至少有一个其他成员。2. 只能作为结构体最后一个成员。3. 声明时不写数组大小语法为 type name[ ] 或 type name[0]0长度时GCC扩展非标准有些编译器写第二个语法时会报错4.柔性数组不占用结构体的内存大小sizeof(struct Example只包含前面的成员内存大小。5. 包含柔性数组的结构体用malloc()函数进行内存的动态分配并且分配的内存应该大于结构体的大小以适应柔性数组的预期大小。代码演示#includestdio.h #includestdlib.h struct Stu { int age; char name[]; }s; int main() { printf(%d\n,sizeof(s)); return 0; }柔性数组的使用#includestdio.h #includestdlib.h struct Stu { int age; char name[]; }; int main() { // 柔性数组获得10字节空间 struct Stu* p (struct Stu*)malloc(sizeof(s) sizeof(char) * 10); for(int i 0;i 10;i) { p-name[i] i; } free(p); return 0; }7. 总结C/C中程序内存区域划分C/C程序内存分配的几个区域1. 栈区在执行函数时函数内局部变量的存储单元都可以在栈区上创建函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中效率高但是分配的内存容量有限。栈区主要存放运行函数中的局部变量、函数参数、返回数据、返回地址等。2. 堆区一般由程序员分配释放若程序员不释放程序结束时可能由OS操作系统回收分配方式类似于链表。3. 数据段静态区static存放全局变量、静态数据。程序结束后由系统释放。4. 代码段存放函数体类成员函数和全局变量的二进制代码。最后感谢阅读欢迎大家交流与指正。
返回列表