C 悬空指针和双自由度

C 悬空指针和双自由度,c,pointers,dangling-pointer,double-free,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\u-aStruct(X)这

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

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\u-aStruct(X)
这样
free\u-aStruct(B)
就可以优雅地退出

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)时,仅执行上述操作设置
A=NULL
被调用<代码>B
现在处于悬空状态


如何避免/补救这种情况?引用计数是唯一可行的解决方案吗?或者,是否有其他“防御”方法来释放内存,以防止
释放(B)爆炸?

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

我不认为你能自动做到这一点,因为C会让你承担管理内存的责任和负担,因此你有责任确保引用,当然还有悬空指针都得到照顾

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);
B = getref_aStruct(A);
自由建筑(A&A);
除此之外,不管是无意编码还是设计错误,您都要自己对“悬空指针”负最终责任。

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

B = A;
在不更改结构内任何内容的情况下复制指针,在没有编译器发出任何警告的情况下绕过所使用的内容。您必须使用以下内容:

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


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

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

引用计数其实并不难:

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
上调用
astruct\u getref()
astruct\u free()
,那么引用计数根本不需要保护——上面的简单实现就足够了)

也就是说,可以轻松扩展上述原语以支持对
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锁
).

迁移到Java最美妙的事情之一是,当我处理这类问题时——我以前每天都要处理的事情——完全消失了。@Bill如果你编写了包含这些问题的代码,你只需要处理这些问题。本质上,你是在问,“如果我写的代码不好,我能防止因为我的代码不好而发生不好的事情吗?“我认为没有任何一种肯定的答案涉及到阳光、光线、小马和兔子。”保罗·内森:别忘了独角兽和彩虹@Neil Butterworth撰写、继承、维护、受雇工作、让队友实施。。。总的来说,我认为Java让你做了太多你应该做的事情,比如拥有公共可变成员和实现大型方法/类。为什么语言让你做一些你永远不应该做的事情?这只会导致人们去“但是如果我不得不这么做呢?”,答案总是——你不必,不应该这么做,如果我不得不看到你的代码,应该阻止我这么做。。。需要设计一个Java——语言:)感谢您的回复。这似乎是一件明智的事情。我的aStruct实际上是一个递归数据结构,我有一个名为节点所有权(self-other)的标志。我有一个新的_aStruct(),它将所有权标志设置为SELF。free_aStruct()现在检查是否在释放子节点之前设置了节点所有权标志。如果需要,B=getref_aStruct(A)将是明确所有权转让的完美场所。这样,一次只有一个对象拥有子节点。getref_aStruct()强制执行纪律。再次感谢你的帮助。最好的,罗斯特汉克斯,这真的很有帮助。这就是我要问的。罗斯特汉克斯咖啡馆。这在我的代码中是绝对可行的。你能提供一个锁的例子吗?最好的,Russ@user151410:我已经更新了答案,添加了一些关于锁定的信息。谢谢,这真的很有帮助。我准备尝试引用计数。你能推荐一些描述参考计数的c语言书吗?谢谢russI我在用valgrind。这真的很有帮助。是否有您推荐的开源静态代码分析器?谢谢,RussGood point。这是一个大问题。它主要是数字代码,并且有标志指示对象是否干净和脏,以防止发生意外