如何在C语言中清理已分配的内存?

5

我写了下面这个玩具程序,观察到第二个变量test2将会占用第一个变量test1释放的内存地址。即使我使用free(test1)释放了test1的内存,test2仍将保留test1的字段值。我想知道如何在C语言中清除free()留下的数据:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct test_str
{
    char name[128];
    int Id;
} test_str;

typedef test_str* mytest;

int main()
{

    mytest test1 = malloc(sizeof(test_str));
    printf("test1 pointer address is %p \n", test1);
    strcpy(test1->name, "hello world");
    test1->Id = 10;

    free(test1);
//  test1->name = NULL;   /* this does not work */
//  test1->Id = 0;        /* without resetting Id = 0, test2->Id will show 10 */
    test1 = NULL;         

    mytest test2 = malloc(sizeof(test_str));
    printf("test2 pointer address is %p, name field is %s, Id = %d \n", test2, test2->name, test2->Id);

    return 0;
}

这是输出结果:

test1指针地址为0x2401010
test2指针地址为0x2401010,名称字段为hello world,ID = 10


1
如果你真的有某种原因想要这样做,那么在释放内存之前,只需用零覆盖数据即可。 - Crowman
你可以随时使用memset - Linuxios
1
这并不重要,因为正确的程序会假定内存内容是不可预测的,并且在写入之前从不尝试读取该存储。 - aschepler
1
释放内存意味着您放弃了所有权利。内存并没有被“销毁”。您已经释放了所有malloc的内容,所以没问题了。剩下的任何东西都不需要被“清理”。 - Mooing Duck
@PaulGriffiths - 你的评论值得回答。不过现在,我想这已经没有意义了。 - ryyker
显示剩余2条评论
6个回答

3
如果你的数据是如此敏感,使用平台调用来锁定内存,并使用非可消除调用到某个memset变体进行清除释放之前。
当你将内存归还给运行时,它可以自由地将其用于下一个合适的请求。标准并没有规定它是否何时这样做,或者它是否将内存归还给可能的操作系统。
附注:void free(void*) {}free的一个有效实现。

“is it fair to say ...is a valid (but useless) implementation of...?” 可以这样说吗:_...是一种有效但无用的实现方式..._? - ryyker
@ryyker 如果你想这么说的话,可以说它对于大多数程序来说是严重低劣的实现。 - Deduplicator

2

正如评论所提到的,只需在free之前使用memset

memset(test1, 0, sizeof(*test1));
free(test1);

1
@Deduplicator - 你在使用印章吗? - ryyker
@ryyker:没有印章,甚至没有复制和粘贴。只是这个评论对于那么多刚刚出现的帖子来说是正确的。 - Deduplicator
@Deduplicator - 纯属开玩笑。你已经很好地为它辩护了 :) - ryyker

2

在地球上几乎没有任何好的理由想要这样做...

然而,在free(test1)之前添加test1->name[0]=0应该可以完成任务。

如果你想要删除先前文本的“所有痕迹”,那么你甚至可以这样做:

for (int i=0; test1->name[i]!=0; i++)
    test1->name[i]=0;

@Deduplicator:什么“不被保证”? - barak manos
编译器可以自由地通过删除循环来进行优化。 - Deduplicator
1
@Deduplicator:“可观察行为”?将非零值设置为零,有什么不“可观察”的呢? - barak manos
1
@barakmanos - 不,那只是一个不好使用强调的地方 :) 只是建议使用memset(但不要告诉Deduplicator) - ryyker
2
@barakmanos:它不会改变程序在命名部分中定义的可观察行为,这意味着它可以被优化掉。这就是“好像”规则。 - Deduplicator
显示剩余6条评论

1
通常情况下,C函数不会做除了它们应该做的事情以外的任何事情。 mallocfree 只是分配和释放内存 - 没有其他操作。这意味着它们不会将任何字段清零。
创建结构体时,一般的技巧是清理相关字段。实际上,考虑编写一个封装结构体创建的函数,如下所示:
mytest create_struct(char* name, int id) {
    mytest t = malloc(sizeof(test_str));
    size_t nlen = strlen(name);
    memcpy(t->name, name, nlen <= 127 ? nlen : 127);
    t->name[127] = '\0';

    t->Id = id;
    return t;
}

如果您愿意,还可以考虑定义相应的删除函数。

void del_struct(mytest t) {
    if (t) {
        memset(t->name, 0, 128);
        t->Id = 0;
        free(t);
    }
}

1

free()并不会清空内存,只是将其释放以供重用,这也正是发生的情况。如果您想要初始化已分配的内存,请考虑使用calloc()。


1

在释放之前,您可以使用memset(test1, 0, sizeof(test_str)),这将简单地用0填充分配的内存区域。

malloc算法实际上通过为其创建的每个内存块保留元数据来工作。因此,如果您在之后立即分配/释放/重新分配相同大小的内存块,则可能会使用完全相同的内存块,这解释了为什么新地址相同。


编译器可以通过删除该调用来进行优化,而且是免费的。 - Deduplicator
根据标准,memset没有可观察的行为,因为您随后直接调用了free - Deduplicator
请注意,标准的5.1.2.3节定义了C程序的行为,因为您的调用不是标准所定义的可观察行为,所以它可以被明确地优化掉。 - Deduplicator
@SanJacinto:不,这是一种安全漏洞,在野外已经遇到过。正因为如此,微软才有了例如RtlSecureZeroMemory的解决方案。 - Deduplicator
1
@Deduplicator:尽管如此,鉴于几乎所有现代系统都提供C标准库作为共享库,并且该库可以在编译程序之后长时间更新,编译器无法推断这里没有可见的副作用,因此无法将其优化掉。5.1.2.3清楚地说明编译器必须能够推断被调用的函数也不会引起这样的副作用。 - Crowman
显示剩余7条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接