Pretty much all talks about Windows's Virtual memory API mentions the fact it allows Dynamic Arrays to be easily used.
You basically Reserve large virtual memory address with VirtualAlloc and Commit memory as you index through the reserved pages. In principle this sounds too simple. But I have been looking and could not find a single example of this. I have been told by a game developer, that this is actually one of the standard way to do dynamic arrays especially in Gamedev. So after a few hours inside Win32 API, I created this minimal example and would appreciate if someone can elaborate if this is actually the right way to do dynamic arrays with the help of virtual memory.
/*
Compile with:
set compiler_flags=/W4 /Zi /nologo /wd4100 /wd4505 /wd4201 /diagnostics:column /EHs- /EHc- /EHa-
set libs=Ole32.lib Kernel32.lib User32.lib
set linker_flags=/INCREMENTAL:NO
cl %compiler_flags% main.cpp %libs% /link %linker_flags%
*/
#include <Windows.h>
#include <stdint.h>
#include <stdio.h>
typedef int32_t s32;
typedef int64_t s64;
typedef int16_t s16;
typedef int8_t s8;
typedef uint32_t u32;
typedef uint64_t u64;
typedef uint16_t u16;
typedef uint8_t u8;
typedef float f32;
typedef double f64;
#define ARRAY_COUNT(Array) (sizeof(Array)/(sizeof(Array[0])))
#define Global static
#define KB(Value) ( (Value) * 1024)
#define MB(Value) (KB(Value) * 1024)
struct dynamic_memory
{
void* MemoryBase;
s32 ReservedPages;
s32 CommittedPages;
};
Global struct memory_table
{
dynamic_memory Arrays[KB(4)];
s32 Used;
} MemoryTable;
Global struct memory_info
{
s32 PageSize;
s32 AllocSize;
} MemoryInfo;
// TODO(ALI) A deallocation function might be used, that decommit the memory
static void*
CreateDynamicArray(SIZE_T AllocSize)
{
// NOTE(ALI) Round Allocation to the system allocation granularity
// NOTE(ALI) This assumes the granularity is a power of two
u64 Mask = MemoryInfo.AllocSize - 1;
AllocSize = (AllocSize + Mask) & (~Mask);
void* Memory = VirtualAlloc(0, AllocSize, MEM_RESERVE, PAGE_READWRITE);
if(!Memory)
{
DWORD ErrorCode = GetLastError();
printf("Failed to allocate with error code = %d\n", ErrorCode);
return (0);
}
s32 MaxSizeArrays = ARRAY_COUNT(MemoryTable.Arrays);
if (MemoryTable.Used >= MaxSizeArrays)
{
printf("You have used too much dynamic arrays \n");
return(0);
}
dynamic_memory Entry = {};
Entry.MemoryBase = Memory;
Entry.ReservedPages = (s32)(AllocSize / MemoryInfo.PageSize); // NOTE(ALI) this assumes allocation size is a multiple of page size, which is reasonable
Entry.CommittedPages = 0;
MemoryTable.Arrays[MemoryTable.Used++] = Entry;
return (Memory);
}
LONG WINAPI
PageFaultHander(struct _EXCEPTION_POINTERS *ExceptionInfo)
{
if(ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION)
{
if(ExceptionInfo->ExceptionRecord->ExceptionInformation[0])
{
void* PageMemory = (void*)ExceptionInfo->ExceptionRecord->ExceptionInformation[1];
s32 DynamicArraysCount = MemoryTable.Used;
s32 Found = 0;
s32 Index = 0;
for(; Index < DynamicArraysCount; ++Index)
{
if(MemoryTable.Arrays[Index].MemoryBase == PageMemory)
{
Found = 1;
break;
}
}
if(!Found)
{
return EXCEPTION_CONTINUE_SEARCH;
}
dynamic_memory* Entry = &MemoryTable.Arrays[Index];
if(Entry->ReservedPages <= 0)
{
printf("Trying to allocate memory but there is no more reserved pages\n");
return EXCEPTION_CONTINUE_SEARCH;
}
void* BaseAddress = (u8*)Entry->MemoryBase;
LPVOID Result = VirtualAlloc( BaseAddress, MemoryInfo.PageSize, MEM_COMMIT, PAGE_READWRITE);
if(!Result)
{
printf("Failed to commit pages with error code = %d\n", GetLastError());
return EXCEPTION_CONTINUE_SEARCH;
}
Entry->CommittedPages++;
Entry->ReservedPages--;
Entry->MemoryBase = (u8*)Entry->MemoryBase + MemoryInfo.PageSize;
}
return EXCEPTION_CONTINUE_EXECUTION;
}
return EXCEPTION_CONTINUE_SEARCH;
}
// STUB entity structure
struct entity
{
f32 PosX;
f32 PosY;
s32 Data;
};
int main(void)
{
//------------ init system info ---------------//
SYSTEM_INFO Info;
GetSystemInfo(&Info);
MemoryInfo.PageSize = Info.dwPageSize;
MemoryInfo.AllocSize = Info.dwAllocationGranularity;
PVOID h1 = AddVectoredExceptionHandler(1 , PageFaultHander);
s32 EntityCount = MB(1);
entity* Entities = (entity*) CreateDynamicArray(EntityCount * sizeof(entity));
entity Empty = {};
for(s32 I = 0; I< EntityCount; ++I)
{
Entities[I] = Empty;
}
RemoveVectoredExceptionHandler(h1);
return(0);
}
This system might leave a lot to be desired, from the way it checks if the page-fault happened within our pages and not some other buggy place, to the fact it always increase by a fixed size and not pre-committing. But I hope it at least demonstrate the general idea.
I have been told that __try / __except block are considered to be generally faster, but I don't think it matters in this case.
Also, since I could not find a single blog entry on google, I would appreciate if someone can link me any blog related to this.