悬空指针和双重免费

时间:2010-04-01 20:17:15

标签: c pointers dangling-pointer double-free

经过一些痛苦的经历,我明白了悬挂指针和双重自由的问题。我正在寻求适当的解决方案。

aStruct包含许多字段,包括其他数组。

aStruct *A = NULL, *B = NULL;
A = (aStruct*) calloc(1, sizeof(sStruct));
B = A;
free_aStruct(A);
...
// Bunch of other code in various places.
...
free_aStruct(B);

有没有办法写free_aStruct(X)以便free_aStruct(B)优雅地退出?

void free_aStruct(aStruct *X) {
    if (X ! = NULL) {
        if (X->a != NULL) { free(X->a); x->a = NULL; }
        free(X); X = NULL;
    }
}

执行上述操作仅在调用A = NULL时设置free_aStruct(A);B现在正悬空。

如何避免/纠正这种情况?引用计数是唯一可行的解​​决方案吗?或者,是否有其他“防御性”方法来释放记忆,以防止free_aStruct(B);爆炸?

5 个答案:

答案 0 :(得分:5)

在简单的C中,这个问题最重要的解决方案是纪律,因为问题的根源在于:

B = A;

制作指针的副本而不更改结构中的任何内容,在没有编译器任何警告的情况下绕过您使用的任何内容。你必须使用这样的东西:

B = getref_aStruct(A);

下一个重要的事情是跟踪分配。有些事情有助于清洁模块化,信息隐藏和干燥 - 不要重复自己。在使用free_aStruct()函数释放它时,直接调用calloc()来分配内存。最好使用create_aStruct()来分配它。这样可以将事物集中在一个地方,而不是在整个代码库中投入内存分配。

对于您在此基础上构建的内存跟踪系统,这是一个更好的基础。

答案 1 :(得分:2)

我不认为你可以自动执行此操作,因为C会给你带来责任和管理内存的负担,因此你有责任确保引用,当然还有悬空指针得到照顾!

void free_aStruct(aStruct *X){
  if (X ! = NULL){
      if (X->a != NULL){free(X->a); x->a = NULL;}
      free(X); X = NULL;
}
}

顺便说一句,上面的if检查中有一个拼写错误...使用小写'x'而不是'X'......

当我查看上面的代码时,我的想法是你在aStruct *类型的指针变量的副本上做一个免费的。我会将其修改为引用调用而不是......

void free_aStruct(aStruct **X){
  if (*X ! = NULL){
      if (*X->a != NULL){
          free(*X->a); 
          *X->a = NULL;
      }
      free(*X); 
      *X = NULL;
  }
}

并称之为:

free_aStruct(&A);

除此之外,无论是无意的编码还是设计错误,你都要对自己的“悬挂指针”负责......

答案 2 :(得分:1)

即使你可以阻止free_aStruct(B)爆炸,如果在你的评论后面的代码中有任何对B的引用,那将会使用已被释放的内存,因此可能会被任何新数据覆盖点。只是“修复”免费通话只会掩盖潜在的错误。

答案 3 :(得分:1)

您可以使用某些技术,但最重要的是,您所做的任何事情都不能在C中严格执行。相反,我建议在开发过程中加入valgrind(或purify)。此外,一些静态代码分析器可能能够检测到其中一些问题。

答案 4 :(得分:1)

引用计数真的不那么难:

aStruct *astruct_getref(aStruct *m)
{
    m->refs++;
    return m;
}

aStruct *astruct_new(void)
{
    sStruct *new = calloc(1, sizeof *new);
    return astruct_getref(new);
}

void astruct_free(aStruct *m)
{
    if (--m->refs == 0)
        free(m);
}

(在多线程环境中,您还可能需要添加锁定)。

然后你的代码将是:

aStruct *A = NULL, *B = NULL;
A = astruct_new();
B = astruct_getref(A);
astruct_free(A);
...
//bunch of other code in various places.
...
astruct_free(B);

你问过锁定问题。不幸的是,在锁定方面没有一个通用的答案 - 这完全取决于您在应用程序中使用的访问模式。精心设计和深刻思考是无可替代的。 (例如,如果您可以保证没有线程将在另一个线程的astruct_getref()上调用astruct_free()aStruct,那么引用计数不需要完全受到保护 - 上面的简单实现就足够了。

也就是说,可以轻松扩展上述原语以支持对astruct_getref()astruct_free()函数的并发访问:

aStruct *astruct_getref(aStruct *m)
{
    mutex_lock(m->reflock);
    m->refs++;
    mutex_unlock(m->reflock);
    return m;
}

aStruct *astruct_new(void)
{
    sStruct *new = calloc(1, sizeof *new);
    mutex_init(new->reflock);
    return astruct_getref(new);
}

void astruct_free(aStruct *m)
{
    int refs;

    mutex_lock(m->reflock);
    refs = --m->refs;
    mutex_unlock(m->reflock);
    if (refs == 0)
        free(m);
}

...但是请注意,包含指向并发访问的结构的指针的任何变量也需要它们自己的锁定(例如,如果你有一个同时访问的全局aStruct *foo,它将会需要随附的foo_lock)。