欢迎光临
我们一直在努力

动态内存管理(2)

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,就能把整块内存全部释放干净,简单又安全。

优势二:有利于访问速度

连续的内存有益于提高访问速度,也有益于减少内存碎片。

赞(0)
未经允许不得转载:171主机测评 » 动态内存管理(2)
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址