tags:

views:

417

answers:

7

Question says it all but here is an example:

typedef struct mutable_t{
    int count, max;
    void **data;
} mutable_t;


void pushMutable(mutable_t *m, void *object)
{
    if(m->count == m->max){
        m->max *= 2;
        m->data = realloc(m->data, m->max * sizeof(void*));
    }
    // how to handle oom??
    m->data[m->count++] = object;
}

How can I handle running out of memory and not NULL out all of my data?

edit - let's assume there is something which could be done e.g. free up some memory somewhere or at least tell the user "you can't do that - you're out of memory". Ideally I would like to leave what was allocated there.

+4  A: 

This is a bit of a hot button topic as there are essentially 2 schools of thought on the subject

  1. Detect the OOM, and having the function return an error code.
  2. Detect the OOM and crash your process as fast as possible

Personally I am in camp #2. Expect for very special types of applications, OOM is fatal period. True, perfectly written code can handle an OOM but so few people understand how to write code that is safe in the face of no memory. Even fewer bother to actually do it because it's almost never worth the effort.

I dislike passing the error code off to the calling function for OOM's because it is the equivalent of telling the caller "I failed and there's nothing you can do about it". Instead I prefer to crash fast so the resulting dump is as instructive as possible.

JaredPar
Things potentially _can_ be done about OOM failure. There's not a lot, but it's possible in some cases. (In most applications, there should be a wrapper around `malloc()` and `realloc()` that just exits with an error message on memory failure, but they don't do that for the few applications with better solutions).
Chris Lutz
@Chris, certainly true and some products (SQL server for example) are quite good at it. However those products are the rare exception. Getting it right requires an amazing amount of discipline, enforcement and understanding. So much so that people rarely even attempt to get it right.
JaredPar
@JaredPar, so you're basically saying because most people don't get error handling right, you shouldn't even bother caring for errors and instead let the application crash and burn, possibly corrupting the user's data? The problem is that OOM happens at runtime on the user's machine. You have no control over memory sizes in these machines and over the HD space for the swap file. Then add memory leaks to it... Plus, it is quite easy to test that your app can handle it. Use a wrapper for malloc/realloc that randomly returns NULL.
Secure
@Secure, what I'm saying is that failing fast as possible is the absolute best way to get an actionable bug report. I deal with a lot of Watson bugs in my position. Code paths which fail fast produce very actionable data and very typically result in a bug being fixed. Code paths which attempt to handle situations like OOM almost always 1) do it incorrectly or 2) pass it off to code which can't handle the situation. Both crash and produce very unactionable bugs since the crash occurs very far after the initial real problem.
JaredPar
+2  A: 

That's entirely your problem! Here are some criteria:

  • You asked for that memory for a reason. If it's not available, is your program's work doomed or can it go on doing stuff? If the former, you want to terminate your program with an error message; otherwise, you can display an error message somehow and go on.

  • Is there a possibility to trade time for space? Could you reply whatever operation you attempted using an algorithm that uses less memory? That sounds like a lot of work but would in effect be a possibility for continuing your program's operation in spite of not having enough memory initially.

  • Would it be wrong for your program to continue limping along without this data and not enough memory? If so, you should terminate with an error message. It is much better to kill your program than to blindly continue processing incorrect data.

Carl Smotricz
+4  A: 

The standard technique is to introduce a new variable to hold the return from realloc. You then only overwrite your input variable if it succeeds:

tmp = realloc(orig, newsize);
if (tmp == NULL)
{
    // could not realloc, but orig still valid
}
else
{
    orig = tmp;
}
R Samuel Klatchko
So it is not set to NULL until the assignment? That is good to know.
Nick
And then what? You didn't try to increase the size of your array for fun, you actually needed that for a reason.
Blindy
@Blindy - fail that operation. Depending on the logic of the app, it will be up to it to decide how to recover (perhaps this is a server and it will fail the one request but continue running other requests). But this looks like low level library code which should not be forcing a out-of-memory policy on the application.
R Samuel Klatchko
@Blindy - If it was a big resize, you could try to make a smaller resize and see if that'll get you by. Otherwise, you should probably print an error message of "No more memory" and exit. Or you can return an error code, and the caller can try to free up some unnecessary memory and try again, if possible. Recovery from a memory error _is_ possible in some situations, even if it's not likely in most.
Chris Lutz
+2  A: 

The first rule that you shoud follow when working with realloc is not to assign the return value of realloc to the same pointer that you passed to it. This

m->data = realloc(m->data, m->max * sizeof(void*)); 

is bad. If realloc fails, it returns null pointer, but it doesn't deallocate the old memory. The above code will null your m->data while the old memory block formerly pointed by m->data will most likely become memory leak (if you have no other references to it).

The return value of realloc should be stored in a separate pointer first

void **new_data;
...
new_data = realloc(m->data, m->max * sizeof(void*)); 

Then you can check for success/failure and change the value of m->data in case of success

if (new_data != NULL)
  m->data = new_data;
else
  /* whatever */;
AndreyT
A: 

There's also another subtle error that can come from realloc. The memory leak coming from returned NULL pointer is rather well known (but quite rare to stumble upon). I had in my program a crash once in a while that came from a realloc call. I had a dynamic structure that adjusted its size automatically with a realloc resembling this one:

m->data = realloc(m->data, m->max * sizeof(void*)); 

The error I made was to not check for m->max == 0, which freed the memory area. And made from my m->data pointer a stale one.

I know it's a bit off-topic but this was the only real issue I ever had with realloc.

tristopia
+1  A: 
  1. Find out how the application framework handles an OOM. Many will simply not handle an OOM. Most of the time a framework will not operate properly in no-free-RAM conditions unless it says very clearly and unambiguously somewhere that it will. If the framework won't handle an OOM and is multithreaded (many are nowadays), an OOM is gonna be the end of the show for the process in a lot of cases. Even if it isn't multithreaded it may still be close to collapse. Whether you exit the process or the framework does may be a moot point; a predictable immediate exit may just be a bit better than a crash out at some semi-random point in the near future.

  2. If you're using a separate special-purpose sub-memory pool (ie not your usual malloc) for a well-defined set of operations that are only constrained in memory use by OOM (ie the current operation is rolled back or aborted cleanly on OOM for the sub-memory pool, not the whole process or main memory pool), and that sub-pool is not also used by the application framework, or if your framework and the WHOLE of the rest of the application is designed to maintain meaningful state and continued operation in no-free-RAM conditions (rare but not unheard of in kernel mode and some types of systems programming) you may be right to return an error code rather than crash the process.

  3. Ideally the bulk of the memory allocations (or even more ideally all the allocations) for a piece of processing should be allocated as soon as possible in processing, ideally before it properly begins, to minimise the problems of data integrity loss and/or amount of rollback coding required if it fails. In practice a lot of the time, to save programming cost and time on projects, to preserve data integrity applications rely on database transactions and requiring the user/support person to detect a GUI crash (or server crash) and restart the app when out of memory errors occur, rather than being written to cope with and rollback on any and all of thousands of potential OOM situations in the best possible ways. Then efforts focus on trying to limit the exposure of the app to overloading situations, which may include additional validation and limits on data size and simultaneous connections and queries.

  4. Even if you check how much memory is reported as available, often other code may alloc or free memory as you do, changing the basis for your memory check and possibly leading to OOM. So checking available free RAM before you alloc is often not a reliable solution to the problem of making sure your application operates within available RAM limits and maintains data integrity enough of the time to satisfy the users.

  5. The best situation to be in is to know how much memory your app requires in all possible cases, including any framework overheads, and to keep that figure within the amount of RAM available to your application, but systems are often so complicated with external dependencies dictating data size so achieving this can be unrealistic.

The acid test of course is are you satisfying the users sufficiently through high up-time, and infrequent data corruption, loss or crashes. In some cases an app having a monitor process to restart it if it crashes is useful.

As regards realloc:

Check the return value from realloc - put it in a temporary variable. Only care if it is NULL if the new size requested was >0. In other cases place it in your non-temporary variable:

eg

    void* temp = realloc(m->data, m->max * sizeof(void*));
    if (m->max!=0&&temp==NULL) { /* crash or return error */ }
    m->data =(void**)temp;

EDIT

Changed "most cases" to "a lot of cases" in (1).

I recognise that you said to assume that "something can be done" if the memory cannot be allocated. But memory management is a very global consideration (!).

martinr
Thank you for the detailed answer
Nick
A: 
Alok