views:

215

answers:

4

Is there a way to determine if free() would fail if ever called on a certain memory block pointer?

I have the following situation: a thread having access to a shared resource fails whilst it may have been in the state of freeing the said resource. Now I need to devise a safe way to clean-up this shared resource.

Of course I have assigned ownership of the resource for the normal case but what about the aforementioned limit case?

UPDATED: If I use additional synchronizing mechanisms it only makes more cleaning up to do and might involved additional limit conditions. I'd like to limit/avoid those if possible.

Resolution: I finally settled on performing re-factoring. Thanks to all contributors. You guys rock!

+2  A: 

I don't believe there is a conforming interface that does what you want.

I can think of a few tricks, however. You could have the failure-prone thread call a wrapper around free() instead of free() directly; the wrapper could save the address or the last few addresses so that you can determine if the block was released. You could also block signals, establish a critical section, or deal with whatever else might interrupt the *thread.

Update: Does the dying thread ever free this memory prior to exit/cleanup? I've frequently written malloc front-ends (as a speed optimization) for memory that doesn't need to be freed in a steady-state. If the thread is just setting itself up, you could malloc the memory prior to thread launch and have the thread call a front-end that just hands out unlinked, unfreeable pieces of the dynamic block. It will run faster and then when the thread dies you can just free the whole block all at once. The general idea here is to have the failure-prone thread get its memory by calling a service that can clean up after-the-fact.

Yet another idea: what about a per-thread heap? If threads could be persuaded to allocate memory needed only during their lifetime from their own heap, that would nicely organize the cleanup task into freeing the entire thread heap when the thread rejoins a parent.

DigitalRoss
@DigitalRoss: thanks for the suggestions. I believe I might not have been clear enough in phrasing my question. I'll update.
jldupont
Writing a wrapper around free() is almost always going to lead to disaster, depending on what *libc returns as a pointer when it fails, or when it operates on one.
Tim Post
Regarding per thread heap, what would happen if something in a structure passed as a thread arg was initially allocated within a thread? You might enjoy looking at "The ANTI thread", as Rusty puts it .. "Threads suck, antithreads try not to." http://ccan.ozlabs.org/info/antithread.html
Tim Post
Antithreads, cool. I actually advise against writing threaded programs except via message passing, functional programming, and actor patterns. "Threads considered harmful".
DigitalRoss
I am all up for messaging passing! That's the idiom I used in my C programs whenever I can. Erlang rules!
jldupont
@DigitalRoss: You can find all kinds of goodies at ccan. Rusty Russel is organizing it, but not a lot of contributors yet. Think CPAN, but for C :)
Tim Post
+4  A: 

I've seen all kinds of attempts, including this one:

void m_free(void **p)
{
        if (*p != NULL) {
                free(*p);
                *p = NULL;
        }
}

Not only does dereferencing a type punned pointer break various platforms, 'plugging this example in' can only work if you initialize, free and re-initialize every single pointer in every single existing function (compiled libs included) and work with a C library that does the same.

Then, deal with optimization AND lock free thread safe issues. BAH!

In short, if you can't keep track of what you have allocated in a single function, its time to re factor that function. Do that enough .. and you'll find that the need for a safer free() quickly goes away. Valgrind is your friend if working on a platform that it supports. According to your tags, it really is your friend :)

Or, use a malloc() that sports garbage collection at your own expense, depending on how you allocate things and get rid of free() altogether. After that, debugging becomes almost terminally interesting.

Hopefully, you are off to re-factor? While you appear to be having an issue (also) with mutual exclusion, it just leads back to re-factoring. I.e, let the line before free() block, or fail when trying to get a lock .. and set freed pointers to NULL in the thread that has the lock, at least in structures that you implement.

Tim Post
@tinkertim: interesting line of reasoning... let me reflect on this.
jldupont
@judpoint: I have since edited my answer for clarification.
Tim Post
+1  A: 

I think there is no way to do exactly what you're asking.

The problem is that there is no way to determine what state the dying thread was in when it died. Did it simply call the free() and get no further? Did free() add the block back to the free list? You can't tell.


If it's a really rare condition for the thread to die in this way (and so it's okay to leave the unfreed memory around - you just want to know not to use it) then the following (using Windows calls) frees the memory and 'marks' it as free to your other threads:

void* ptr;
...
void* desiredPtr = ptr;
if(InterlockedCompareExchangePointer(&ptr, NULL, desiredPtr) == desiredPtr)
  free(desiredPtr);

What this does is it makes sure that ONLY one thread is trying to free the memory and before it does it sets the address to NULL so no other thread will try to free() it.


If it's unacceptable for the memory to be occationally kept around then the best way might be to have a separate thread whose only job is to free memory. The other threads can then queue up free requests for the free-memory-thread. Since the free-memory-thread is so simple it should never die and can finish the free operation properly.

Aaron
A: 

If you are calling free with a valid pointer, I don't see how it would fail. If it is failing, it must be due to an invalid pointer.

In addition to synchronizing access to shared memory (with a mutex, for example), ownership must be clear too to avoid cases like double-freeing. Double-freeing is when two or more threads have a valid pointer, but then more than one thread attempts to free the memory. Despite the second thread having a non-null pointer, it is no longer valid.

If you are plagued with memory issues in C/C++, you might try a memory library like HeapAgent. A memory library like this will instrument and initialize each memory allocation. Before freeing memory, it checks if the memory pointer is valid first and there were no buffer overrun errors. There should be no code changes as it can simply replace the built-in malloc/free. In addition, the library can help find memory leaks, overwrites, and invalid references.

Another strategy to address your problem may be to centralize the resource cleanup to one thread. When a thread is done with the resource, it just marks it available to be cleaned up by the garbage collector thread.

Of course, then there is Plan C -- use Java... Just kidding.

AWhitford
...the whole point of this question is centered on: "I am unsure of the state of the pointer". If it was only up to me, I would be on Erlang at the moment but in this instance, I have no choice but to C.
jldupont
A strategy like what is used by HeapAgent is the only way I can think of: When you malloc(x), you actually malloc(metadata+x+endtag) and populate the beginning with metadata (how big the allocation is), and tag the end of the allocation with some unique sequence like 0xDEAD. Then you return the pointer+metadata to send back x. Now every malloc response is actually pointing to a subsequence of a real malloc call. This is useful when freeing because the free call can do the pointer math to find the metadata and confirm the endtag. If all is well, then the allocation is freed.
AWhitford
Garbage collection in C is dangerous. It leads to very lazy and inefficient fruit loops that 'just work'. I.e. for (i=1; i<10; i++) { foo = strdup("bar"); } That should tell you that you need to spend 10 minutes implementing some way of operating on an existing allocated pointer via realloc(), or re-factor your method :)
Tim Post