Summing up some comments & research, I first take down some info about memory to be able to explain some approach I found.
There are three kinds of things people use to call "memory":
- Virtual memory (when wrongly used for "virtual address space")
- heap space (that is either physical RAM space or page file space)
- (real) physical memory space
When you use new (or HeapAlloc or VirtualAlloc with MEM_COMMIT), the OS reserves a virtual address range, allocates heap space and maps both.
You can use AWE (e.g. AllocateUserPhysicalPages) to allocate physical memory w/o mapping it to a virtual address range (use VirtualAlloc with MEM_RESERVE | MEM_PHYSICAL to make up for the mapping).
Now, to take some advantages from this, I'm sorry i need to go into detail and unfortunately write a lot of text on this memory topic. I tried to point out the important things using bold print.
Possible approaches in out-of-memory conditions to log / report an error:
- You cannot rely on CRT or STL to behave "correctly" under out-of-memory conditions.
- You can pre-allocate a chunk of memory (that is, virtual memory / adress space & physical / page file memory) and use it for buffers
- You cannot (simply) pre-allocate a chunk of memory and free it to provide free memory to CRT and other functions, since freed memory (= physical memory) can be allocated by every thread and process
- You can use HeapLock (Win32) to prevent other threads of your current process from allocating / deallocating heap space
- There are at least two heaps in your app: the process's heap (GetProcessHeap()) and the CRT heap (_get_heap_handle())
- the standard new operator internally uses HeapAlloc on the CRT heap (!)
- memory is split into pages, you can determine their size with GetSystemInfo(), dwPageSize
- You can allocate whole pages of physical memory (w/o virtual memory mapping) using AWE (AllocateUserPhysicalPages, VirtualAlloc to reserve address space, MapUserPhysicalPages)
- You can use HeapReAlloc with HEAP_REALLOC_IN_PLACE_ONLY to re-size an allocated chunk of memory
Now, my approach: [note: I edited it to work with HeapReAlloc and only one buffer]
- If you just need some buffers to work
with, use either
stack space global/static variables (.BSS / .DATA / ...) in your
c++ program or pre-allocated heap
space (even better: pre-allocated physical memory -> AWE). Stack space Global/static variables are probably a
better idea since they're part of your .EXE and therefore do not need to be allocated (-> no exceptions)
- If you have to use CRT / STL / Win32 functions, you have to make sure they a) do not require heap space or b) there is enough heap space available
Of course, the trick is to provide heap space to CRT / ... functions when an out-of-memory condition appears (indicated by an bad_alloc, for example). They internally use malloc / HeapAlloc, that means they try to allocate heap space and reserve a virtual address range. So, if you have only some pre-allocated buffers, you had to prevent further memory allocation and remap their allocation requests to your pre-defined buffer. You could make use of _set_new_handler and / or overwrite the global new op, but for me, this didn't work (std::string yet called the normal new).
So, if you cannot redirect allocation queries, you have to provide free physical memory (or at least heap space) that can only be allocated by your process (= that is locked for other processes)! To do this, you could make use of the (physical!) memory layout AFAIK, i.e. that the physical memory is split into pages. Consider the following approach:
- When initialising you error reporter, (if you have more than one thread already running), lock the heap and pre-allocate a a chunk of memory at the size of one memory page; make sure it's aligned to a page and does not cover two pages.
- Unlock the heap and begin to work (don't forget to use a try-catch!).
- When catching the bad_alloc, do not try to use CRT or STL or whatever functions you don't know the code of. Of course, do not try to allocate memory.
- In your error handler, lock the heap and resize your pre-allocated buffer in place.
- You now have exactly that size you freed to work with; you can use STL or CRT functions if they do not exceed the free heap space.
- Don't forget to unlock the heap!
All of it relys on HeapAlloc and HeapReAlloc: When you allocate a whole memory page and later re-allocate it in place (resize it), you still should have that page reserved, but with free space on it.
In code, it's something like:
SYSTEM_INFO sSysInfo;
GetSystemInfo(&sSysInfo);
HANDLE hCRTHeap = (HANDLE)_get_heap_handle();
BYTE* pcBackupBuffer = (BYTE*)HeapAlloc(hCRTHeap, 0, sSysInfo.dwPageSize - 1);
try
{
for(size_t i = 0; i < 0xFFFFFFFF; i++)
{
BYTE* pc = new BYTE[32];
}
printf("NO bad_alloc thrown at the for loop\r\n");
}catch(bad_alloc&)
{
printf("bad_alloc thrown at the for loop\r\n");
}
try
{
BYTE* test = new BYTE[32];
printf("NO bad_alloc thrown at the first test\r\n");
}catch(bad_alloc&)
{
printf("bad_alloc thrown at the first test\r\n");
}
/*pcBackupBuffer = */HeapReAlloc(hCRTHeap, HEAP_REALLOC_IN_PLACE_ONLY, pcBackupBuffer, 1);
try
{
BYTE* test = new BYTE[200];
printf("NO bad_alloc thrown at the second test\r\n");
}catch(bad_alloc&)
{
printf("bad_alloc thrown at the second test\r\n");
}
Output:
bad_alloc thrown at the for loop
bad_alloc thrown at the first test
NO bad_alloc thrown at the second test
On Vista x64, VS 2008, x86 Debug, after allocating approx. 2.069.692 K RAM (says taskman). Should be out-of-virtual-address-space exceptions.
Further testing with 2 instances, enhanced code (enhanced output and tests), build against x64 / Release w/o debugger attached, 4 GB RAM + max. 2 GB page file showed similar behaviour concerning the HeapReAlloc feature. Just some strange behaviour: Even after throwing a bad_alloc, sometimes an equal allocation (some lines below) still works w/o exception o.O
Some words about the sizeing:
- I used the BYTE[32] in the for loop because the new op actually allocates a bit more (in debug mode) than the size passed as parameter, and I wanted to allocate each possible mem page so that the first test will really fail (and allocating only one byte each time is really sloooow)
- the second test fails allocating more memory than freed, I don't know exactly why. Maybe the page was filled up with the BYTE[32] from the for loop, so I've to figure out the exact amount to allocate from the page of pcBackupBuffer
It's all kind of a dirty hack, so I have to admit: use this only in special (emergency ^^) cases and do not to rely on it. But you might get 3 or 4 K of memory, and even get further semi-pages repeating this trick. I think that might be enough for simple CRT / Win32 / STL functions to operate on.