3

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

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);爆炸?

4

5 回答 5

5

在纯 C 语言中,解决这个问题最重要的是纪律,因为问题的根源在这里:

B = A;

在不更改结构中的任何内容的情况下复制指针,绕过您使用的任何内容,而无需编译器发出任何警告。你必须使用这样的东西:

B = getref_aStruct(A);

下一个重要的事情是跟踪分配。一些有用的东西是干净的模块化、信息隐藏和 DRY——不要重复自己。您直接调用 calloc() 来分配内存,同时使用 free_aStruct() 函数来释放它。最好使用 create_aStruct() 来分配它。这使事情集中在一个地方,而不是在整个代码库中分配内存。

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

于 2010-04-01T20:42:10.833 回答
2

我不认为您可以自动执行此操作,因为 C 将您的责任和负担置于管理内存,因此您有责任确保引用和悬空指针得到照顾!

无效free_aStruct(aStruct *X){
  如果(X!= NULL){
      if (X->a != NULL){free(X->a); x->a = NULL;}
      免费(X);X = 空;
}
}

顺便说一句,上面的检查中有一个错字if……使用小写“x”而不是“X”……

当我查看上面的代码时,我的想法是,您正在对 type 的指针变量的副本进行免费操作aStruct *。我会将其修改为按引用调用...

void free_aStruct(aStruct **X){
  如果 (*X ! = NULL){
      if (*X->a != NULL){
          免费(*X->a);
          *X->a = NULL;
      }
      免费(* X);
      *X = 空;
  }
}

并这样称呼它:

free_aStruct(&A);

除此之外,无论是无意编码还是设计错误,您最终都要对“悬空指针”负责……

于 2010-04-01T20:38:51.980 回答
1

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

于 2010-04-01T20:28:39.640 回答
1

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

于 2010-04-01T21:16:01.007 回答
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()andastruct_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)。

于 2010-04-02T01:17:25.503 回答