织梦制作手机网站模板,广东官网网站建设平台,wordpress工具栏,奎屯网站建设文章目录 引言一、为什么要动态内存分配二、动态内存分配的相关函数2.1 malloc2.2 free2.3 calloc2.4 realloc 三、常见的动态内存的错误3.1 对NULL指针的解引用3.2 对动态内存越界访问3.3 对非动态内存释放3.4 对动态内存部分释放3.5 对动态内存多次释放3.6 未对动态内存释放内存泄漏 四、动态内存经典笔试题分析4.1 题目一4.2 题目二4.3 题目三4.4 题目四 五、柔性数组5.1 柔性数组的特点5.2 柔性数组的使用 六、C/C中程序内存区域划分 引言 学习专栏 《零基础学C语言》 《数据结构世界》 俗话说的好要想学好数据结构数据结构世界对数据结构感兴趣的小伙伴可以移步就必须学好以下三方面知识
指针 不允许你还不了解指针的那些事一内存和地址指针变量指针运算野指针传址调用 不允许你还不了解指针的那些事二数组传参的本质冒泡排序数组指针指针数组结构体 自定义类型结构体你真的掌握了内存对齐位段吗动态内存管理
前两方面的知识在往期已经详细讲解今天我们就来学习最后一方面的知识——动态内存管理
一、为什么要动态内存分配
我们已经掌握的内存开辟方式有
int val 20;//在栈空间上开辟四个字节
char arr[10] {0};//在栈空间上开辟10个字节的连续空间但是上述的开辟空间的方式有两个特点
空间开辟大小是固定的。数组在申明的时候必须指定数组的长度数组空间⼀旦确定了大小不能调整
所以为了能在程序运行中根据需求灵活地调整空间大小C语言引入了动态内存管理。
二、动态内存分配的相关函数
2.1 malloc
C语言提供了一个动态内存开辟的函数
void* malloc (size_t size);这个函数可以开辟一块连续的内存空间并返回指向这块空间的指针
如果开辟成功则返回指向开辟好空间的指针。如果开辟失败则返回NULL 指针因此malloc的返回值一定要做检查。返回值的类型是 void* 所以malloc函数并不知道开辟空间的类型具体在使用的时候使用者自己来决定。
int main()
{//动态开辟10个整型空间int* a (int*)malloc(10 * sizeof(int));if (a NULL)//开辟不成功返回NULL{perror(malloc fail);return 1;}//开辟成功使用该空间//...return 0;
}注意malloc(0)——开辟0个字节空间是标准未定义行为具体取决于编译器
2.2 free
C语言提供了另外一个函数free可以动态内存的释放和回收
void free (void* ptr);free函数用来释放动态开辟的内存。
如果参数 ptr 是指向动态内存空间的指针则释放该空间如果参数 ptr 是NULL指针则不作任何处理。
int main()
{//动态开辟10个整型空间int* a (int*)malloc(10 * sizeof(int));if (a NULL)//开辟不成功返回NULL{perror(malloc fail);return 1;}//开辟成功使用该空间//使用完毕释放该空间free(a);a NULL;return 0;
}注意如果参数 ptr 指向的空间不是动态开辟的那free函数的行为是未定义的
2.3 calloc
C语言还提供了一个函数叫 calloc calloc 函数也用来动态内存分配。
void* calloc (size_t num, size_t size);其实它与malloc很相似区别在于calloc会将开辟的连续空间每个字节都初始化为0
int main()
{//动态开辟10个整型空间并初始化为0int* a (int*)calloc(10, sizeof(int));if (a NULL)//开辟不成功返回NULL{perror(calloc fail);return 1;}//开辟成功使用该空间//使用完毕释放该空间free(a);a NULL;return 0;
}2.4 realloc
realloc函数的出现让动态内存管理更加灵活。 为了合理的运用内存所以在开辟空间后觉得太大或太小就可以使用realloc进行调整。
void* realloc (void* ptr, size_t size);realloc调整成功会返回调整后指向这块空间的指针
ptr 是调整前空间的指针size 是调整后的新大小这个函数调整原内存空间大小的基础上还会将原来内存中的数据移动到新的空间。
realloc在调整内存空间的是存在两种情况
情况1原有空间之后有足够大的空间
这个时候realloc会直接在原有空间后面直接追加开辟。
情况2原有空间之后没有足够大的空间
这个时候realloc就会再找一块足够大空间一次性开辟完再将原数据拷贝过去。
int main()
{//动态开辟10个整型空间int* a (int*)malloc(10 * sizeof(int));if (a NULL)//开辟不成功返回NULL{perror(malloc fail);return 1;}//调整空间扩大为20个整型空间int* tmp (int*)realloc(a, 20 * sizeof(int));if (tmp NULL)//开辟不成功返回NULL{perror(realloc fail);return 1;}//调整成功将空间地址给aa tmp;//使用空间...free(a);a NULL;return 0;
}注意
用临时变量tmp来接受realloc调整后的空间地址因为如果用a接收万一调整失败返回NULL还会丢掉原本空间的地址
三、常见的动态内存的错误
3.1 对NULL指针的解引用
void test()
{int *p (int *)malloc(INT_MAX/4);*p 20;//如果p的值是NULL就会有问题free(p);
}3.2 对动态内存越界访问
void test()
{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.3 对非动态内存释放
void test()
{int a 10;int *p a;free(p);//ok?
}3.4 对动态内存部分释放
void test()
{int *p (int *)malloc(100);p;free(p);//p不再指向动态内存的起始位置
}3.5 对动态内存多次释放
void test()
{int *p (int *)malloc(100);free(p);free(p);//重复释放
}3.6 未对动态内存释放内存泄漏
因为程序终止会回收所有空间所以这里用死循环来模拟程序运行中不释放动态内存会导致内存泄漏。
int main()
{int* a (int*)malloc(10 * sizeof(int));while(1);return 0;
}总结动态开辟的内存一定要释放并且要正确释放
四、动态内存经典笔试题分析
请先自行思考哦不要立马看解析~
请问以下题目运行Test 函数会有什么样的结果
4.1 题目一
void GetMemory(char *p)
{p (char *)malloc(100);
}
void Test(void)
{char *str NULL;GetMemory(str);strcpy(str, hello world);printf(str);
}结果
程序崩溃内存泄漏
解析
GetMemory函数中动态开辟了100个字节空间。但是p是一个形参形参是实参的一份临时拷贝对形参的影响无法改变实参所以str还是NULL。在运行strcpy函数时因为要拷贝的目的地是NULL所以程序崩溃。同时动态开辟的空间未释放还导致了内存泄漏。
4.2 题目二
char *GetMemory(void)
{char p[] hello world;return p;
}
void Test(void)
{char *str NULL;str GetMemory();printf(str);
}结果
有可能打印出乱码
解析
GetMemory函数中开辟了p数组。p是局部变量出了函数作用域就会销毁空间被回收。所以str接收p的地址时此时已经指向一块被回收的空间。打印该空间的内容是否为乱码取决于该空间的内容是否被其余数据覆盖
4.3 题目三
void GetMemory(char **p, int num)
{*p (char *)malloc(num);
}
void Test(void)
{char *str NULL;GetMemory(str, 100);strcpy(str, hello);printf(str);
}结果
内存泄漏有可能程序崩溃
解析
这次进行传址调用二级指针解引用确实能改变外部str但是接收到空间地址后未进行检查如果开辟失败为NULL则strcpy时依旧会程序崩溃同时未对动态内存释放造成内存泄漏
4.4 题目四
void Test(void)
{char *str (char *) malloc(100);strcpy(str, hello);free(str);if(str ! NULL){strcpy(str, world);printf(str);}
}结果
后果未知
解析
假设能正常开辟动态内存strcpy拷贝完hello并free进行释放因为free不会自动将str置为NULL所以以下判断不起作用“拦不住”str这个野指针最后strcpy将world拷贝到已被释放的空间再进行打印对已被释放的动态内存进行操作后果未知十分危险很严重
五、柔性数组
也许你从来没有听说过柔性数组flexible array这个概念但是它确实是存在的。 C99 中结构中的最后⼀个元素允许是未知大小的数组这就叫做『柔性数组』成员。
typedef struct st_type
{int i;int a[0];//柔性数组成员
}type_a;有些编译器会报错无法编译可以改成
typedef struct st_type
{int i;int a[];//柔性数组成员
}type_a;5.1 柔性数组的特点
结构中的柔性数组成员前面必须至少一个其他成员。sizeof 返回的这种结构大小不包括柔性数组的内存。包含柔性数组成员的结构用malloc ()函数进行内存的动态分配并且分配的内存应该大于结构的大小以适应柔性数组的预期大小。
typedef struct st_type
{int i;int a[0];//柔性数组成员
}type_a;
int main()
{printf(%d\n, sizeof(type_a));//输出的是4return 0;
}5.2 柔性数组的使用
int main()
{int i 0;type_a *p (type_a*)malloc(sizeof(type_a)100*sizeof(int));//使用空间p-i 100;for(i0; i100; i){p-a[i] i;}free(p);return 0;
}这样柔性数组成员a相当于获得了100个整型元素的连续空间。
六、C/C中程序内存区域划分 C/C程序内存分配的几个区域
栈区stack在执行函数时函数内局部变量的存储单元都可以在栈上创建函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中效率很高但是分配的内存容量有限。 栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。堆区heap一般由程序员分配释放若程序员不释放程序结束时可能由OS回收。分配方式类似于链表。数据段静态区static存放全局变量、静态数据。程序结束后由系统释放。代码段存放函数体类成员函数和全局函数的二进制代码。
看到这里了还不给博主扣个 ⛳️ 点赞☀️收藏 ⭐️ 关注 ❤️ 拜托拜托这个真的很重要 你们的点赞就是博主更新最大的动力 有问题可以评论或者私信呢秒回哦。