1.动态内存经典笔试题
1.1题目一:
Test函数运行后会出现什么情况?
为什么会出现下面的错误?

第一个问题:空指针解引用
在C语言中,函数参数采用值传递机制,当你将指针变量str传入Getmemory时,实际传递的是str当前值NULL的一份副本,这份副本由形参p接收。

函数内执行p=(char*)malloc(100)时,仅修改了p的指向,让它指向了malloc在堆上开辟的100字节内存,原变量str并没有改变,依然为NULL。而后续执行strcpy(str,"hello world")时,strcpy会尝试向NULL地址写入字符串,这属于非法访问内存,程序就会崩溃。
第二个问题:内存泄漏
出了Getmemory这个函数后,形参p会被销毁,这块内存的地址也随之丢失,后续我们也无法使用free()释放这块内存,它会一直被程序占用,直到进程结束。
那么我们如何去改造这个代码,使之正常运行呢?
改造1:

改造2:


1.2题目二:
Test函数运行后会出现什么情况?

为什么打印结果是一堆乱码?

出函数后,p数组的地址会传给str,但p所在的占内存空间会被系统回收,原数据可能被覆盖或清空,str变成了指向无效内存的悬空指针,所以无法正确打印出“hello,world”。
在char p前加上static就能正确打印了。
1.3题目三:
Test函数运行后会出现什么情况?

注意,在部分运行环境下,程序可能短暂输出“world",但这只是未定义行为的偶然表现,本质上str已被free成为野指针,strcpy向已经释放的内存写入数据,属于非法访问,随时可能触发程序崩溃等问题。
2.柔性数组
C99中,结构体中的最后一个元素允许是未知大小的数组,这就叫做<柔性数组>成员。
例如:
struct st_type
{
int i;
};
int a[0];
有些编译器会报错无法编译,也可以改成:
struct st_type
{
int i;
};
int a[];
2.1柔性数组的特点:
· 结构体中的柔性数组成员前面必须至少一个其他成员
· sizeof返回的这种结构大小不包括柔性数组的内存,如下图:

· 包含柔性数组的结构用malloc函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。

2.2柔性数组的使用

这样柔性数组成员arr就相当于获得了五个整形元素的连续空间。
那么柔性数组的“柔性”又是如何体现的?我们接着上述代码继续写:

如上图,我们可知在后续的代码中利用realloc就能使数组的空间大小柔性地变化,由此体现出柔性数组的特点。
当然,我们也能通过不使用柔性数组的方法来达到相同的目的,如下面的代码二。
2.3对比
代码一:
struct S
{
int n;
int arr[];//5
};
int main()
{
struct S* ps = (struct S*)malloc(sizeof(struct S) + 5 * sizeof(int));
if (ps == NULL)
{
perror("malloc");
return 1;
}
ps->n = 100;
int i = 0;
for(i=0;i<5;i++)
{
ps->arr[i] = 5;
}
struct S* ptr=realloc(ps, sizeof(struct S) + 10 * sizeof(int));
if (ptr != NULL)
{
ps = ptr;
ptr = NULL;
}
else
{
perror("realloc");
return 1;
}
//继续使用
//释放
free(ps);
ps = NULL;
return 0;
}
代码二:
struct S
{
int n;
int* arr;
};
int main()
{
struct S* ps=(struct S*)malloc(sizeof(struct S));
if (ps == NULL)
{
perror("malloc");
return 1;
}
ps->n = 100;
ps->arr = malloc(5 * sizeof(int));
if (ps->arr == NULL)
{
perror("malloc");
return 1;
}
//使用
int i = 0;
for (i = 0; i < 5; i++)
{
ps->arr[i] = 5;
}
//增加空间
int*ptr = realloc(ps->arr, 10 * sizeof(int));
if (ptr == NULL)
{
perror("realloc");
return 1;
}
ps->arr = ptr;
ptr = NULL;
//使用
// …
//释放
free(ps->arr);
free(ps);
ps = NULL;
return 0;
}
在实现相同功能的情况下,代码一有明显的优势。
2.4柔性数组的优势
优势一:方便内存释放
如果我们的代码是封装成函数给别人调用,用<代码二>(结构体里嵌套独立指针,二次内存分配)的方式时,会存在隐患:
· 我们在函数内部做了两次内存分配(一次给结构体,一次给数组),然后把结构体指针返回给用户。
· 用户只知道要free这个结构体指针,却不知道结构体里的数组成员也要单独释放,很容易造成内存泄露。
而使用柔性数组的思路,把【结构体本身+数组所需要的空间】一次性分配好再返回给用户,用户只需要调用一次free,就能把整块内存全部释放干净,简单又安全。
优势二:有利于访问速度
连续的内存有益于提高访问速度,也有益于减少内存碎片。

