Linux Optimistic Malloc: Will New Always Throw When Out of Memory

Linux optimistic malloc: will new always throw when out of memory?

It depends; you can configure the kernel's overcommit settings using vm.overcommit_memory.

Herb Sutter discussed a few years ago how this behavior is actually nonconforming to the C++ standard:

"On some operating systems, including specifically Linux, memory allocation always succeeds. Full stop. How can allocation always succeed, even when the requested memory really isn't available? The reason is that the allocation itself merely records a request for the memory; under the covers, the (physical or virtual) memory is not actually committed to the requesting process, with real backing store, until the memory is actually used.

"Note that, if new uses the operating system's facilities directly, then new will always succeed but any later innocent code like buf[100] = 'c'; can throw or fail or halt. From a Standard C++ point of view, both effects are nonconforming, because the C++ standard requires that if new can't commit enough memory it must fail (this doesn't), and that code like buf[100] = 'c' shouldn't throw an exception or otherwise fail (this might)."

Always check malloc'ed memory?

Depends on the platform. For instance, on Linux (by default) it does not make much sense to check for NULL:

http://linux.die.net/man/3/malloc

By default, Linux follows an optimistic memory allocation strategy. This means that when malloc() returns non-NULL there is no guarantee that the memory really is available. This is a really bad bug. In case it turns out that the system is out of memory, one or more processes will be killed by the infamous OOM killer.

malloc does not guarantee returning physically contiguous memory

malloc does not guarantee returning physically contiguous memory

yes

It guarantees returning virtually contiguous memory

yes

Especially it is true when size > 4KB because 4KB is a size of page.
( On Linux systems).

Being contiguous memory does not imply that it will also be page aligned. The allcated memory can start from any address in heap. So whatever OS uses the page size it does not affect the allocation nature of malloc.

Why does malloc() or new never return NULL?

Linux, by default, usually uses an opportunistic memory allocation scheme, meaning the kernel will give you a valid address that won't be allocated until first use.

See:

  • SIGKILL while allocating memory
  • C Program on Linux to exhaust memory

According to those responses you can turn this feature off using echo 2 > /proc/sys/vm/overcommit_memory.

From what I can tell, this is done under the assumption that you wont necessarily use all the memory that you allocate. I can't say that I personally ever allocate space that I don't touch at least once, so I'd be curious to know how this affects real life performance...

Regarding the SIGKILL failure, every malloc you call is still allocating some memory for each call. Eventually you will likely fill your memory with malloc overhead and thus evoke the fury of the out of memory kill feature. Whether this alone is the issue or if perhaps the overcommit policy still allocates some fraction of the requested space is a good question.

Should I bother detecting OOM (out of memory) errors in my C code?

Out of memory conditions can happen even on modern computers with lots of memory, if the user or system administrator restricts (see ulimit) the memory space for a process, or the operating system supports memory allocation limits per user. In pathological cases, fragmentation makes this fairly likely, even.

However, since use of dynamically allocated memory is prevalent in modern programs, for good reasons, it becomes very hairy to handle out-of-memory errors. Checking and handling errors of this kind would have to be done everywhere, at high cost of complexity.

I find that it is better to design the program so that it can crash at any time. For example, make sure data the user has created gets saved on disk all the time, even if the user does not explicitly save it. (See vi -r, for example.) This way, you can create a function to allocate memory that terminates the program if there is an error. Since your application is designed to handle crashes at any time, it's OK to crash. The user will be surprised, but won't lose (much) work.

The never-failing allocation function might be something like this (untested, uncompiled code, for demonstration purposes only):

/* Callback function so application can do some emergency saving if it wants to. */
static void (*safe_malloc_callback)(int error_number, size_t requested);

void safe_malloc_set_callback(void (*callback)(int, size_t))
{
safe_malloc_callback = callback;
}

void *safe_malloc(size_t n)
{
void *p;

if (n == 0)
n = 1; /* malloc(0) is not well defined. */
p = malloc(n);
if (p == NULL) {
if (safe_malloc_callback)
safe_malloc_callback(errno, n);
exit(EXIT_FAILURE);
}
return p;
}

Valerie Aurora's article Crash-only software might be illuminating.

C++ constructor: which exception type should I throw when malloc fails to allocate memory

The most appropriate exception would be to throw std::bad_alloc; however it is strongly discouraged to use malloc unless you have a good reason to -- and so I would advise against throwing this explicitly.

If you absolutely need heap memory, you should be using new/delete -- which will automatically invoke constructors/destructors, start/end object lifetimes, and will throw a std::bad_alloc when out of memory for you. std::malloc will result in undefined behavior for any non-trivial types because it doesn't formally start an object's lifetime.

Better yet, c++11 has been out for over ten years, so use std::unique_ptr and, if you have c++14, use std::make_unique so you don't even need new directly. unique_ptr will prevent memory leaks by ensuring that delete is called on the allocated pointer if the pointer is not-null.

This would now look like:

#include <memory>

// Note: 'typedef struct' does nothing in C++ -- there is no need for it
struct Point {
float x;
float y;
};

class Foo {
private:
std::unique_ptr<Point> p;
public:
Foo ()
: p{std::make_unique<Point>()}
{

}
};

Aside from the above answer: You shouldn't really use heap memory unless you really have a need for objects with dynamic lifetime. In the code you provided, it looks like it may be more appropriate to just hold the Point by value anyway:

class Foo {
private:
Point p;
public:
Foo ()
: p{} // no allocation needed
{

}
};

Memory usage doesn't increase when allocating memory

The C++ runtime typically allocates a block of memory when a program starts up, and then parcels this out to your code when you use things like new, and adds it back to the block when you call delete. Hence, the operating system doesn't know anything about individual new or delete calls. This is also true for malloc and free in C (or C++)

What's the graceful way of handling out of memory situations in C/C++?

Well, if you are in a case where there is a failure to allocate memory, you're going to get a std::bad_alloc exception. The exception causes the stack of your program to be unwound. In all likelihood, the inner loops of your application logic are not going to be handling out of memory conditions, only higher levels of your application should be doing that. Because the stack is getting unwound, a significant chunk of memory is going to be free'd -- which in fact should be almost all the memory used by your program.

The one exception to this is when you ask for a very large (several hundred MB, for example) chunk of memory which cannot be satisfied. When this happens though, there's usually enough smaller chunks of memory remaining which will allow you to gracefully handle the failure.

Stack unwinding is your friend ;)

EDIT: Just realized that the question was also tagged with C -- if that is the case, then you should be having your functions free their internal structures manually when out of memory conditions are found; not to do so is a memory leak.

EDIT2: Example:

#include <iostream>
#include <vector>

void DoStuff()
{
std::vector<int> data;
//insert a whole crapload of stuff into data here.
//Assume std::vector::push_back does the actual throwing
//i.e. data.resize(SOME_LARGE_VALUE_HERE);
}

int main()
{
try
{
DoStuff();
return 0;
}
catch (const std::bad_alloc& ex)
{ //Observe that the local variable `data` no longer exists here.
std::cerr << "Oops. Looks like you need to use a 64 bit system (or "
"get a bigger hard disk) for that calculation!";
return -1;
}
}

EDIT3: Okay, according to commenters there are systems out there which do not follow the standard in this regard. On the other hand, on such systems, you're going to be SOL in any case, so I don't see why they merit discussion. But if you are on such a platform, it is something to keep in mind.



Related Topics



Leave a reply



Submit