8

Probably everyone ran into this problem at least once during development:

while(/*some condition here that somehow never will be false*/)
{
    ...
    yourvector.push_back(new SomeType());
    ...
}

As you see the program starts to drain all system memory, your program hangs and your system starts to swap like crazy. If you don't recognize the problem fast enough and kill the process you probably get an unresponsive system in seconds where your mouse pointer don't even moving. You can either wait your program crash with "out of memory" error (which may take several long minutes) or hit the reset on your computer.

If you can't track down the bug immediately then you will need several tests and resets to find out which is very annoying...

I'm looking for a possibly cross-platform way to prevent this somehow. The best would be a debug mode code that exits the program if it allocated too much memory, but how can I keep track how much memory is allocated? Overriding the global new and delete operators won't help, because the free function I would invoke in the delete won't give any idea how many bytes are freed.

Any ideas appreciated.

Calmarius
  • 18,570
  • 18
  • 110
  • 157
  • 1
    What operating system are you using? How much RAM? What's the size of the paging file? Essential to know. – Hans Passant Jul 24 '10 at 20:40
  • If you are prone to this sort of error, just turn off swap. I've got a heavily used development machine in front of me with an uptime of over a month. The high-water mark for maximum swap used is 22MiB. (linux 2.6.32 4GiB core, swapiness 60) – msw Jul 24 '10 at 20:41

5 Answers5

13

If you're on a Linux or Unix-ish system, you could check into setrlimit(2) which allows you to configure resource limits for your program. You can do similar things from the shell with ulimit.

Steven Schlansker
  • 37,580
  • 14
  • 81
  • 100
  • +1 because it is another thing I didn't know about. But I want my program to be cross platform, so sometimes I develop it under Windows sometimes under Linux and my problem can happen at both sides. – Calmarius Jul 26 '10 at 17:39
10

Overriding the global new and delete operators won't help, because the free function I would invoke in the delete won't give any idea how many bytes are freed.

But you can make it so. Here's a full framework for overloading the global memory operators (throw it in some global_memory.cpp file):

namespace
{   
    // utility
    std::new_handler get_new_handler(void)
    {
        std::new_handler handler = std::set_new_handler(0);
        std::set_new_handler(handler);

        return handler;
    }

    // custom allocation scheme goes here!
    void* allocate(std::size_t pAmount)
    {

    }

    void deallocate(void* pMemory)
    {

    }

    // allocate with throw, properly
    void* allocate_throw(std::size_t pAmount)
    {
        void* result = allocate(pAmount);

        while (!result)
        {
            // call failure handler
            std::new_handler handler = get_new_handler();
            if (!handler)
            {
                throw std::bad_alloc();
            }

            handler();

            // try again
            result = allocate(pAmount);
        }

        return result;
    }
}

void* operator new(std::size_t pAmount) throw(std::bad_alloc)
{
    return allocate_throw(pAmount);
}

void *operator new[](std::size_t pAmount) throw(std::bad_alloc)
{
    return allocate_throw(pAmount);
}

void *operator new(std::size_t pAmount, const std::nothrow_t&) throw()
{
    return allocate(pAmount);
}

void *operator new[](std::size_t pAmount, const std::nothrow_t&) throw()
{
    return allocate(pAmount);
}

void operator delete(void* pMemory) throw()
{
    deallocate(pMemory);
}

void operator delete[](void* pMemory) throw()
{
    deallocate(pMemory);
}

void operator delete(void* pMemory, const std::nothrow_t&) throw()
{
    deallocate(pMemory);
}

void operator delete[](void* pMemory, const std::nothrow_t&) throw()
{
    deallocate(pMemory);
}

Then you can do something like:

    // custom allocation scheme goes here!
    const std::size_t allocation_limit = 1073741824; // 1G
    std::size_t totalAllocation = 0;

    void* allocate(std::size_t pAmount)
    {
        // make sure we're within bounds
        assert(totalAllocation + pAmount < allocation_limit);

        // over allocate to store size
        void* mem = std::malloc(pAmount + sizeof(std::size_t));
        if (!mem)
            return 0;

        // track amount, return remainder
        totalAllocation += pAmount;
        *static_cast<std::size_t*>(mem) = pAmount;

        return static_cast<char*>(mem) + sizeof(std::size_t);
    }

    void deallocate(void* pMemory)
    {
        // get original block
        void* mem = static_cast<char*>(pMemory) - sizeof(std::size_t);

        // track amount
        std::size_t amount = *static_cast<std::size_t*>(mem);
        totalAllocation -= pAmount;

        // free
        std::free(mem);
    }
GManNickG
  • 494,350
  • 52
  • 494
  • 543
  • Storing the size on the allocated space is a good idea. Why didn't it come to my mind... Also +1 for pointing out that I should override the throwing and non-throwing versions of new and delete I completely forgot them. :) – Calmarius Jul 26 '10 at 17:37
1

because the free function I would invoke in the delete won't give any idea how many bytes are freed

It can, you'll just have to keep a map of the size of allocated memory by address, and subtract the right amount based on that information during the free.

Wim
  • 11,091
  • 41
  • 58
  • STL map would use the global new and delete too unless you give him your own allocator that independent from the global one. I see a possibility of an infinite recursion. – Calmarius Jul 25 '10 at 07:09
0

You could implement you own global new operator:

void* operator new (std::size_t size) throw (std::bad_alloc);
void* operator new (std::size_t size, const std::nothrow_t& nothrow_constant) throw();
void* operator new (std::size_t size, void* ptr) throw();

void* operator new[] (std::size_t size) throw (std::bad_alloc);
void* operator new[] (std::size_t size, const std::nothrow_t& nothrow_constant) throw();
void* operator new[] (std::size_t size, void* ptr) throw();

Then, just set a hard limit about how much memory you allocate; maybe even how moch Kb/sec

Gianni
  • 4,300
  • 18
  • 24
0

If you want an easy way to find all those potential leaks, simply use your text editor and search for .push_back in all of your source code. Then examine all occurances of that function call and see if they reside inside of a tight loop. That may help you find some bad problems in the code. Sure you may get 100 hits, but that can be examined in a finite amount of time. Or you could write a static analyzer (Using Scitools API) to find all while loops that have a container method called .push_back that is called inside of them.

C.J.
  • 15,637
  • 9
  • 61
  • 77