3

After some painful experiences, I understand the problem of dangling pointers and double free. I am seeking proper solutions.

aStruct has a number of fields including other arrays.

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);

Is there any way to write free_aStruct(X) so that free_aStruct(B) exits gracefully?

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

Doing the above only sets A = NULL when free_aStruct(A); is called. B is now dangling.

How can this situation be avoided / remedied? Is reference counting the only viable solution? Or, are there other "defensive" approaches to freeing memory, to prevent free_aStruct(B); from exploding?

Palec
  • 12,743
  • 8
  • 69
  • 138
user151410
  • 776
  • 9
  • 22
  • 1
    One of the nicest things about moving to Java was that when I did this entire CLASS of concerns--something I used to have to deal with daily, went away completely. – Bill K Apr 01 '10 at 20:21
  • 4
    @Bill You only have to deal with these concerns if you write code that has them. –  Apr 01 '10 at 20:30
  • 3
    You are asking, in essence, "if I write bad code, can I prevent bad things from happening because of my bad code?". I don't think there's any sort of *yes* answer that involves sunshine, light, ponies, and bunnies. – Paul Nathan Apr 01 '10 at 20:44
  • @Paul Nathan: Don't forget the unicorns and rainbows! – Fred Larson Apr 01 '10 at 20:49
  • @Neil Butterworth write, inherit, maintain, get hired to work on, have a teammate implement, ... Overall I actually think Java lets you do WAY too much stuff you should do like have public mutable members and implement large methods/classes. Why should a language let you do something you should never do? It can only lead to people going "But what if I have to???" when the answer is always--you don't have to, shouldn't do it and, if I ever have to see your code, should have been prevented from doing... Need to design a Java-- language :) – Bill K Apr 01 '10 at 21:20
  • @Bill If you think Java prevents you from doing "things you should never do", I have a bridge to sell you. But I'm not clear if that is what you do think, as your comment seems to be completely incoherent. –  Apr 01 '10 at 21:23
  • @Neil Butterworth Just replying to your point that you only have those concerns if you write code that way--I have to work almost exclusively on code with some pretty serious bad design decisions that I didn't make. – Bill K Apr 01 '10 at 23:26

5 Answers5

5

In plain C, the most important solution to this problem is discipline, because the root of the problem is here:

B = A;

Making a copy of the pointer without changing anything within your struct, circumventing whatever you use without any warning from the compiler. You have to use something like this:

B = getref_aStruct(A);

The next important thing is to keep track of the allocations. Some things that help are clean modularization, information hiding and DRY -- Don't Repeat Yourself. You directly call calloc() to allocate the memory while you use a free_aStruct() function to free it. Better use a create_aStruct() to allocate it. This keeps things centralized and in one place only, instead of throwing memory allocations all over your codebase.

This is a much better base for whatever memory tracking system you build on top of this.

Secure
  • 4,268
  • 1
  • 18
  • 16
  • Thanks for the response. This seems like a sensible thing to do. My aStruct is really a recursive data-structure and I do have a flag called node-ownership (self-other). I do have new_aStruct() which sets the ownership flag to SELF. free_aStruct() now checks to see if the node-ownership flag is set before freeing subnodes. B= getref_aStruct(A) would be a perfect place to make transfer of ownership explicit, if so desired. That way only one object owns the sub-nodes at a time. getref_aStruct() forces one to exercise discipline. Thanks again for your help. Best, Russ – user151410 Apr 01 '10 at 21:24
2

I do not think you can do this automatically as C places the onus and burden of you to manage the memory and therefore your responsibility to ensure that references and of course dangling pointers are looked after!

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

By the way, there's a typo blip in the if check above ... use of lower case 'x' instead of 'X'...

My thinking when I was looking at the above code is that you are doing a free on a copy of a pointer variable of type aStruct *. I would modify it to be a call-by-reference instead...

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

And call it like this:

free_aStruct(&A);

Other than that, you are ultimately responsible for the 'dangling pointers' yourself whether its an unintentional coding or a design fault...

t0mm13b
  • 34,087
  • 8
  • 78
  • 110
1

Even if you could prevent the free_aStruct(B) from blowing up, if there's any reference to B in the code behind your comment, that's going to be using memory that's been freed, and so might be overwritten with new data at any point. Just "fixing" the free call will only mask the underlying error.

Andy Mortimer
  • 3,619
  • 20
  • 14
  • Good point. That is a big problem. It is mostly numeric code and there are flags indicating if the object is clean and dirty, to prevent unintended access. But, in the end as posts on this thread indicate, it is difficult to enforce in c. My hope is to use these techniques and various tools valgrind etc. to ensure that there are no illegal accesses. Russ – user151410 Apr 02 '10 at 19:02
1

There are techniques you can use but the bottom line is that nothing you do can be strictly enforcable in C. Instead, i recommend incorporating valgrind (or purify) in your development process. Also, some static code analyzers may be able to detect some of these problems.

frankc
  • 11,290
  • 4
  • 32
  • 49
  • I am using valgrind. that really helps. Are there open source static code analyzers that you would recommend? Thanks, Russ – user151410 Apr 02 '10 at 19:01
1

Reference counting's really not that hard:

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);
}

(In a multithreaded environment you will also potentially need to add locking).

Then your code would be:

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);

You've asked about locking. Unfortunately there's no one-size-fits-all answer when it comes to locking - it all depends on what access patterns you have in your application. There's no substitute for careful design and deep thoughts. (For example, if you can guarantee that no thread will be calling astruct_getref() or astruct_free() on another thread's aStruct, then the reference count doesn't need to be protected at all - the simple implementation above will suffice).

That said, the above primitives can easily be extended to support concurrent access to the astruct_getref() and astruct_free() functions:

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);
}

...but note that any variables containing the pointers to the structs that are subject to concurrent access will need their own locking too (for example, if you have a global aStruct *foo that is concurrently accessed, it will need an accompanying foo_lock).

caf
  • 233,326
  • 40
  • 323
  • 462
  • Thanks caf. That is definitely doable within my code. Could you provide an example of locking as well? Best, Russ – user151410 Apr 02 '10 at 18:58
  • @user151410: I've updated my answer to add some information about locking. – caf Apr 02 '10 at 23:47
  • Thanks, that really helps. I am ready to attempt reference counting. Could you recommend some c books describing reference counting? thanks russ – user151410 Apr 07 '10 at 00:48