Is there a way to determine if free() would fail?

2019-07-17 16:58发布

问题:

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!

回答1:

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.



回答2:

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.



回答3:

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.



回答4:

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.