(More on using free()d memory, and/or malloc() later returning a pointer
value that was at one point free()d.)
[There is a] global
variable instList, this is the pointer to the whole list. When an
instance is to be freed, it is first removed from the list, and then
free()-d (see code). So all pointers in the list are always valid.
Unfortunately I cannot keep track of all copies of these pointers that
fly around in the application. Thats why the whole problem arises.
Let me boil this down to some sample code:
/* xalloc - just like malloc, but panics (aborts) if out of memory */
extern void *xalloc(size_t);
struct obj {
struct obj *next;
... data ...
};
static struct obj *head; /* "global" list head */
/* create a new object, putting it in the list */
struct obj *newobj(void) {
struct obj *new = xalloc(sizeof *new);
new->next = head;
head = new;
...
return new;
}
/* delete an object, which must be in the list */
void deleteobj(struct obj *obj) {
struct obj *p, **pp;
for (pp = &head; (p = *pp) != NULL; pp = &p->next)
if (p == obj)
break;
if (p == NULL)
panic("asked to delete object not in list");
/* at this point, p==obj and it is in the list */
*pp = p->next;
free(p);
}
/* return true if the object is in the list */
int testobj(struct obj *obj) {
struct obj *p;
for (p = head; p != NULL; p = p->next)
if (p == obj)
return 1;
return 0;
}
Now you can call testobj() with a valid pointer, to see if it is
in the global list:
struct obj *p1, *p2;
p1 = newobj();
p2 = newobj();
if (testobj(p1)) ...
if (testobj(p2)) ...
These are all OK (albeit useless; obviously p1 and p2 *are* in
the list at this point).
Here is where things go wrong:
void f2(void) {
struct obj *p1 = newobj();
use(p1);
if (testobj(p1)) ...
...
}
where use() looks like this:
void use(struct obj *p) {
deleteobj(p);
}
Here, the problem is that use() got a copy of the value in p1, and
asked deleteobj() to delete the object. But its caller (f2()) then
continues to use the object.
This is not something that can be detected, in general. One might
as well ask f2() to detect code like this:
void use(struct obj *p) {
memcpy(p, 0xff, 10000); /* smash a bunch of memory, whee! */
}
The problem is not in f2(); the problem is in use(). It cannot be
solved in f2(); it must be solved in use(). The use() function
lights the fuse on a time-bomb; there is no predicting just when
it will go off.
This does not mean that debug routines -- code that gives you a
chance to set the bomb off faster -- are *useless*, just that they
can never be made perfect. The bomb may be trickier than the
defusing code.
If you could find a machine that actually used segment registers
to catch illegal use of free()d pointers, you would not even need
the debug routine. Instead of a delayed-fuse time-bomb, the original
use() -- which incorrectly deletes the object while it is still in
use) -- would set a *non*-delayed fuse that would trigger immediately
upon the return to f2(), when f2() attempts to do any other operation
on p1 (except of course something like "p1 = NULL").
Alas, nobody builds machines designed for debugging anymore.
Now, if you *want* to be able to "pseudo-delete" objects, and
have them only go away when everyone is *really* done with them,
you need a garbage collector, or something that is sufficiently
similar. In this case, you might change "obj" to add a reference
count to it, and have gainref() and deleteref() calls, with the
object going away when its last reference goes away:
struct obj {
int refcnt;
... data ...
};
/* create a new object */
struct obj *newobj(void) {
struct obj *new = xalloc(sizeof *new);
new->refcnt = 1;
...
return new;
}
/* gain an extra reference to an object */
void gainref(struct obj *p) {
p->refcnt++;
}
/* lose a reference to an object */
void deleteref(struct obj *p) {
if (p->refcnt <= 0)
panic("invalid refcnt in deleteref");
if (--p->refcnt == 0)
free(p);
}
Now a function can just add a reference at any time, and delete
one whenever it is done. When the reference count goes to zero,
the object goes away; but not until then.
Of course, a reference counting system only works if you do not
have loops in the data structures (or if those data structures do
not "count themselves", as it were, but this last is terribly
tricky). In this case, you might be better served by a language
that includes built-in garbage-collection.