【C语言进阶】之动态内存管理笔试题
- 1.动态内存管理笔试题汇总
- 1.1第一道题
- 1.2第二道题
- 1.3第三道题
- 1.4第四道题
- 2.C/C++内存管理
- 3.柔性数组
- 3.1什么是柔性数组
- 3.2柔性数组的使用
- 3.2柔性数组的优点
📃博客主页: 小镇敲码人
🚀 欢迎关注:👍点赞 👂🏽留言 😍收藏
🌏 任尔江湖满血骨,我自踏雪寻梅香。 万千浮云遮碧月,独傲天下百坚强。 男儿应有龙腾志,盖世一意转洪荒。 莫使此生无痕度,终归人间一捧黄。🍎🍎🍎
❤️ 什么?你问我答案,少年你看,下一个十年又来了 💞 💞 💞
前言:接上一篇博客【C语言进阶】之动态内存管理,今天来跟着博主把理论应用在实践之中,彻底掌握动态内存管理的相关知识!!!!
1.动态内存管理笔试题汇总
1.1第一道题
char *GetMemory(void)
{
char p[] = "hello world";
return p;
}
void Test(void)
{
char *str = NULL;
str = GetMemory();
printf(str);
}
请问运行Test函数会有怎样的结果呢?
运行结果:
解析:数组p
是函数里开的一个临时变量,它的空间开在栈区上,出了GetMemory
函数作用域它的生命周期结束,系统就把它的空间回收了,所以你返回的地址是系统已经回收的地址,里面的内容是未知的,是一串字符。
另外printf打印字符串,可以直接传字符串首元素的地址打印。
1.2第二道题
void GetMemory(char *p)
{
p = (char *)malloc(100);
}
void Test(void)
{
char *str = NULL;
GetMemory(str);
strcpy(str, "hello world");
printf(str);
}
运行结果:
解析:程序直接崩溃,这是为什么呢?明明这次已经在堆区开辟空间了呀,实际上,我们并没有改变str,如果你不相信,我们可以来验证一下:
在调用函数GetMemory后为什么str的值没有改变呢?因为str和p都是一级指针,它们两个唯一的相似之处就是它们的值是一样的,本质还是值传递,想改变一级指针的值,需要传它的地址,我们把代码这样改就对了:
#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);
free(str);
str = NULL;
}
int main()
{
Test();
return 0;
}
1.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);
}
请问运行Test 函数会有什么样的结果?
运行结果:
这道题和上一道题我们修改后的类似不做过多阐述,但是有个问题,虽然编译器没有报错,但是它动态开辟的空间没有释放,会造成内存泄露。
1.4第四道题
void Test(void)
{
char *str = (char *) malloc(100);
strcpy(str, "hello");
free(str);
if(str != NULL)
{
strcpy(str, "world");
printf(str);
}
}
请问运行Test 函数会有什么样的结果?
运行结果:
解析:
虽然运行结果没有问题,但其实这段代码是不对的,原因就是我们在free动态开辟的空间后没有及时的去将str
置为空指针,使他成为了一个野指针,那片地址已经不属于我们了,你还进行写入和访问,显然是非法的,所以我们应该养成好习惯,free后就要将指针变量置空。
2.C/C++内存管理
C/C++内存分配的几个区域:
1.栈区(stack):栈区主要放一些函数中创建的局部变量,生命周期在函数结束时就结束了,内存也被系统(OS)回收,这个区域通常存放临时变量、函数的参数、函数的返回数据、返回地址等。读写效率高,但是容量有限。
2.堆区(heap):这个区域主要是动态开辟的内存存放的地方,通常只有程序结束时系统才会回收其内存,除非你手动使用去回收,像C语言中使用的free函数。
3.数据段(静态区 static):这个内存区域主要存放静态变量、全局变量,生命周期也是全局,只有程序运行结束系统才会回收其空间。
4.代码段:这个区域内存主要存放函数体(类成员函数和全局函数)的二级制代码。
有了上面那幅图,相信你对动态内存管理又有了更深的理解,也可以更好的理解static
这个关键字了:
1.一般的临时变量开在栈区,栈区的特点是,出了作用域,里面的临时变量就会被销毁。
2.static
修饰的静态变量在数据段,生命周期变长了,在程序运行结束的时候它的空间才会被系统回收。
3.柔性数组
3.1什么是柔性数组
C99标准中,如果结构体的最后一个数组它的大小是未知的,我们就把那个数组叫做柔性数组。
typedef struct ss
{
int a;
int b[0];
}flexarr;
如果上面那种你的编译器不能通过,可以尝试写成下面这样:
typedef struct sss
{
int a;
int b[];
}flexarr;
关于柔性数组有几点需要说明的:
1.sizeof计算结构体的大小时,是不将柔性数组的大小计算在内的。
2.当你要给柔性数组用malloc()
函数开空间时,大小应该要比结构体的大小要大,以便于系统给柔性数组分配空间。
3.柔性数组前面必须要有至少要有一个成员。
如果你不相信我们可以用下面代码来验证一下:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
struct ss
{
int a;
int b[0];
}flexarr;
int main()
{
printf("%d", sizeof(flexarr));
return 0;
}
运行结果:
3.2柔性数组的使用
通过下面代码我们来演示柔性数组的使用:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
typedef struct ss
{
int a;
int b[0];
}flexarr;
int main()
{
flexarr* p = (flexarr*)malloc(sizeof(flexarr) + 100 * sizeof(int));
if (p == NULL)
{
perror("malloc failed");
exit(-1);
}
p->a = 100;
memset(p->b, 0, sizeof(int) * 100);
for (int i = 0; i < 100; i++)
{
p->b[i] += 1;
}
printf("%d\n", p->a);
for (int i = 0; i < 100; i++)
{
printf("%d ", p->b[i]);
}
free(p);
return 0;
}
运行结果:
flexarr* p = (flexarr*)malloc(sizeof(flexarr) + 100 * sizeof(int));
这个代码相当于给柔性数组开了100个int的空间。
3.2柔性数组的优点
上述flexarr
也可以这样设计:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
typedef struct ss
{
int a;
int* b;
}flexarr;
int main()
{
flexarr* p = (flexarr*)malloc(sizeof(flexarr) + 100);
if (p == NULL)
{
perror("malloc failed");
exit(-1);
}
p->a = 100;
p->b = (int*)malloc(sizeof(int) * 100);
memset(p->b, 0, sizeof(int) * 100);
for (int i = 0; i < 100; i++)
{
p->b[i] += 1;
}
printf("%d\n", p->a);
for (int i = 0; i < 100; i++)
{
printf("%d ", p->b[i]);
}
free(p->b);
p->b = NULL;
free(p);
p = NULL;
return 0;
}
运行结果:
第一种方法和第二种相似,但是柔性数组有一定的优势:
1.方便内存释放
- 如果我们的代码是在一个函数里面,给用户使用,用户是看不到我们具体的实现,给用户返回一个结构体,你在里面进行二次内存分配,用户只知道释放结构体的大小,怎么能知道还需要释放里面的动态数组呢?这样就会造成内存泄漏,不安全。
- 2.提高了访问速度
代码1使用柔性数组,只进行了一次内存分配,是连续的,可以提高访问速度,减少了内存碎片。