How to handle realloc when it fails due to memory?
Asked Answered
L

8

27

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.

Lithiasis answered 31/12, 2009 at 18:27 Comment(4)
highly dependent on application... but one thing is for sure, an OOM is pretty critical.Uncharitable
related: #1941823Uncharitable
Just to add to a couple answers here, an idea for how to handle a failed realloc() (in your case) would be to do m->max /= 4; m->max *= 3; and try calling realloc() again to see if we can still squeeze a few more bytes out. You could even try a couple times with successively smaller sizes, but at some point it won't be worth it.Pearlstein
if (!m->data) { log("Please upgrade to the 64-bit version"); abort(); }Adlay
S
36

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;
}
Sake answered 31/12, 2009 at 18:36 Comment(7)
So it is not set to NULL until the assignment? That is good to know.Lithiasis
And then what? You didn't try to increase the size of your array for fun, you actually needed that for a reason.Atelier
@Atelier - 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.Sake
@Atelier - 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.Pearlstein
Do I need to free also tmp or not?Whereunto
@Whereunto - if tmp is NULL, then it doesn't matter (free(NULL) in all modern malloc implementations). If tmp is not NULL, then yes you need to free it but you need to free it at the correct time. orig = malloc(size) ... tmp = realloc(orig, newsize) if (tmp == NULL) { free(orig); ... give up ... } else { orig = tmp; } ... free(orig);Sake
So, since it's a pointer, I have always to free orig, however. Right?Whereunto
L
12

The strategy about what to do when realloc() fails depends upon your application. The question is too generic to be answered for all possible cases.

Some other notes:

Never do:

a = realloc(a, size);

If realloc() fails, you lose the original pointer, and realloc() does not free() the original memory, so you will get a memory leak. Instead, do:

tmp = realloc(a, size);
if (tmp)
    a = tmp;
else
    /* handle error */

Second point I want to make is minor and may not be that critical, but it's good to know about it anyway: increasing the memory to be allocated by a factor f is good. Let's say you malloc() n bytes first. Then you need more memory, so you realloc() with size n×f. Then you need more memory, so you need n×f2 bytes. If you want realloc() to use the space from the previous two memory blocks, you want to make sure that n×f2 ≤ n + n×f. Solving this equation, we get f≤ (sqrt(5)+1)/2 = 1.618 (the Golden ratio). I use a factor of 1.5 most of the times.

Libidinous answered 31/12, 2009 at 20:8 Comment(2)
do you have any further material on memory allocation algorithms?Scotch
Aren't you at risk of trying a huge but not needed allocation? I have an several arrays with 10^9 elements, and I may need to realloc two of them. The code already takes 10% of the memory and I am afraid realloc fails. I was thinking to realloc(old_size + 1000), but I understand that, in general, this may cause many calls to realloc. Would that be bad? (It should not be my case now, but in the future...)Mascia
C
9

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.

Cheney answered 31/12, 2009 at 18:30 Comment(4)
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).Pearlstein
@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.Cheney
@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.Golfer
@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.Cheney
F
3

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 */;
Frederiksen answered 31/12, 2009 at 18:42 Comment(0)
H
2

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.

Hosanna answered 31/12, 2009 at 18:32 Comment(0)
I
2
  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 (!).

Ioyal answered 31/12, 2009 at 19:29 Comment(0)
A
1

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.

Affirmatory answered 31/12, 2009 at 18:51 Comment(1)
The fun thing I just discovered now (i.e. in 2016) is that the stdlib I used at that time did not follow correctly the standard, as realloc() is required to return NULL in the case of a call with 0 length. This would not have triggered the bug in the first place. Fascinating, because I remember very well that bug, that happenned around 2004 on a very old (already for that time) Solaris machine.Ponce
N
1

I have come across the problem.The configuration is OS:win7(64);IDE:vs2013;Debug(Win32).
When my realloc returned null due to memory in.I have two solutions to it:

1.Change the project's property, to enable LARGE ADDRESSES.
2.Change my solution platform from Win32 to x64.

Nagel answered 26/2, 2016 at 4:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.