Skip to content

File Mapped Memory Allocator

A C++ class that implements a full page-based memory allocator (Alloc, CAlloc, ReAlloc, Free, FreeAll) on top of a file-backed memory-mapped region.

Published Mar 14, 2000

The CFileMemory class is a self-contained memory manager built on Win32 file mapping: it opens (or creates) a file, maps a fixed-size view into the process, and then runs its own first-fit page allocator over that region. Allocations are chains of pages tracked by per-page attribute flags (USED, STARTOFCHAIN, ENDOFCHAIN, MANAGER), so multi-page blocks can be grown in place with ReAlloc when contiguous free pages follow them, or relocated otherwise. It persists across runs - if you open the same file without overwrite, the allocation map survives.

Use it when you want to experiment with how an allocator works, need scratch RAM larger than physical memory backed by a file, or want a template for shared-memory segments with their own bookkeeping. The Test() method is a good read too: it exhausts the pool with uniform and non-uniform allocations, frees everything, then writes/reads every byte of the mapped region to verify integrity.

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma once
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/*
	Memory mapping & handing routines.
*/
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#define ONEKILOBYTE                       1024ul
#define ONEMEGABYTE                    1048576ul
#define ONEGIGABYTE                 1073741824ul

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#define MEMORY_DEFAULT_PAGE_SIZE		128ul //Memory allocation granularity.
#define MEMORY_PAGE_ATTRIB_USED			0x1u  //Pages that are allocated.
#define MEMORY_PAGE_ATTRIB_STARTOFCHAIN	0x2u  //Pages that are allocated.
#define MEMORY_PAGE_ATTRIB_ENDOFCHAIN	0x4u  //Pages that are the tail of a "chain" of pages.
#define MEMORY_PAGE_ATTRIB_MANAGER		0x8u  //Pages that are used by the memory manager itsself.

typedef struct _tag_Memory_Manager_Info {
	unsigned char *uAttribs; //Array of page attributes.
	size_t uUsed;			 //The number of pages that have been allocated (count of non-free pages).
	size_t uCount;			 //The total number of memory manager pages (([Total Mamory] - [Kernel Reserved]) / [Allocation Granularity]).
	size_t uManagerPages;	 //The number of pages reserved my the memory manager for its internal use.
	size_t uSize;			 //Number of bytes in each page.
	size_t uBaseAddress;	 //The base address for all allocations.
} MEMORYMANAGERPAGES, *LPMEMORYMANAGERPAGES;

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

class CFileMemory {
public:
	void *CAlloc(size_t uBytes);
	void *CAlloc(size_t uElementCount, size_t uElementSize);
	void *Alloc(size_t uBytes);
	void *Alloc(size_t uElementCount, size_t uElementSize);
	void *ReAlloc(void *pAddress, size_t uTotalBytes);
	bool Free(void *pAddress);
	int FreeAll(void);

	CFileMemory();
	CFileMemory(char *sFileName, bool bOverwrite, size_t uiTotalRam);
	CFileMemory(char *sFileName, bool bOverwrite, size_t uiTotalRam, size_t uiPageSize);
	~CFileMemory();

	bool Initialize(char *sFileName, bool bOverwrite, size_t uiTotalRam);
	bool Initialize(char *sFileName, bool bOverwrite, size_t uiTotalRam, size_t uiPageSize);
	void Destroy();

	void GetPageInfo(MEMORYMANAGERPAGES *pMMP);
	bool Test(void);

	MEMORYMANAGERPAGES *Pages;

private:
	HANDLE hMemoryFileHandle = NULL;
	HANDLE hMemoryMapHandle = NULL;

	void *AllocP(size_t uBytes, unsigned char uAttributes);
	void *AllocP(size_t uElementCount, size_t uElementSize, unsigned char uAttributes);
	void *ReAllocP(void *pAddress, size_t uTotalBytes, unsigned char uAttributes);

	void *pBaseMemAddress;		//Base of system memory.
};

The heart of it is Initialize, which does the Win32 plumbing - create/open the backing file, build the mapping, view it, and lay the manager structure at the front of the region:

bool CFileMemory::Initialize(char *sFileName, bool bOverwrite, size_t uiTotalRam, size_t uiPageSize)
{
	bool fileAreadyExists = false;
	if (bOverwrite == false)
	{
		DWORD dwAttrib = GetFileAttributes(sFileName);
		fileAreadyExists = (dwAttrib != INVALID_FILE_ATTRIBUTES && !(dwAttrib & FILE_ATTRIBUTE_DIRECTORY));
	}

	this->hMemoryFileHandle = CreateFile(sFileName,
		GENERIC_READ | GENERIC_WRITE,
		FILE_SHARE_READ | FILE_SHARE_WRITE,
		NULL,
		bOverwrite ? CREATE_ALWAYS : OPEN_ALWAYS,
		FILE_ATTRIBUTE_NORMAL,
		NULL
	);
	if (this->hMemoryFileHandle == NULL)
	{
		return false;
	}

	LARGE_INTEGER li;
	memset(&li, sizeof(li), 0);
	li.QuadPart = uiTotalRam;
	this->hMemoryMapHandle = CreateFileMapping(this->hMemoryFileHandle, NULL, PAGE_READWRITE, li.HighPart, li.LowPart, NULL);
	if (this->hMemoryMapHandle == NULL)
	{
		CloseHandle(this->hMemoryFileHandle);
		return false;
	}

	this->pBaseMemAddress = MapViewOfFile(this->hMemoryMapHandle, FILE_MAP_WRITE | FILE_MAP_READ, 0, 0, uiTotalRam);
	if(this->pBaseMemAddress == NULL)
	{
		CloseHandle(this->hMemoryFileHandle);
		CloseHandle(this->hMemoryMapHandle);
		return false;
	}

	//Initialize the global memory manager structure.
	this->Pages = (MEMORYMANAGERPAGES *)pBaseMemAddress;

	if (fileAreadyExists == false)
	{
		memset(this->Pages, 0, sizeof(MEMORYMANAGERPAGES));
	}

	this->Pages->uAttribs = (unsigned char *)(((size_t)pBaseMemAddress) + sizeof(MEMORYMANAGERPAGES));
	this->Pages->uSize = uiPageSize;
	this->Pages->uCount = uiTotalRam / Pages->uSize;
	this->Pages->uBaseAddress = (size_t)pBaseMemAddress;

	size_t uiManagerBytesRequired = ((sizeof(unsigned char) * Pages->uCount)) + sizeof(MEMORYMANAGERPAGES);
	this->Pages->uManagerPages = uiManagerBytesRequired / Pages->uSize;

	//Make sure we get a full page if we spill over with a misaligned allocation of manager bytes.
	if ((uiManagerBytesRequired % Pages->uSize) != 0)
	{
		this->Pages->uManagerPages++;
	}

	//Round-up the manager Pages->
	if (((sizeof(unsigned char) * Pages->uCount) % Pages->uSize) > 0)
	{
		Pages->uManagerPages++;
	}

	if (fileAreadyExists == false)
	{
		//Initialize all of the page attribs to zero.
		memset(Pages->uAttribs, 0, sizeof(unsigned char) * Pages->uCount);
	}

	//Reserve memory for internal use by the manager.
	for (size_t uPage = 0; uPage < Pages->uManagerPages; uPage++)
	{
		Pages->uAttribs[uPage] = MEMORY_PAGE_ATTRIB_USED | MEMORY_PAGE_ATTRIB_MANAGER;
		Pages->uUsed++;
	}
	//Mark the last block in a chain of pages as the "End of Chain".
	Pages->uAttribs[Pages->uManagerPages - 1] |= MEMORY_PAGE_ATTRIB_ENDOFCHAIN;

	return true;
}

And the core first-fit search in AllocP - scan past the manager's own pages, count consecutive free pages, and claim the run once enough are found:

void *CFileMemory::AllocP(size_t uBytes, unsigned char uAttributes)
{
	if (uBytes <= 0)
	{
		return NULL; //Requested an invalid number or bytes.
	}

	//Figure out how many pages we need.
	size_t uPagesRequired = (uBytes / Pages->uSize) + ((uBytes % Pages->uSize) > 0);
	size_t uContiguousPages = 0;

	//Search for contiguous Pages.
	for (size_t uPage = Pages->uManagerPages; uPage < Pages->uCount; uPage++)
	{
		if (!(Pages->uAttribs[uPage] & MEMORY_PAGE_ATTRIB_USED))
		{
			if (++uContiguousPages == uPagesRequired)
			{
				size_t uStartingPage = ((uPage - uContiguousPages) + 1);

				//Reserve the memory Pages-> The last page in the block is not chained to the next page.
				for (uContiguousPages = 0; uContiguousPages < uPagesRequired; uContiguousPages++)
				{
					this->Pages->uAttribs[uPage - uContiguousPages] = MEMORY_PAGE_ATTRIB_USED | uAttributes;
					Pages->uUsed++;
				}

				this->Pages->uAttribs[uStartingPage] = MEMORY_PAGE_ATTRIB_USED | MEMORY_PAGE_ATTRIB_STARTOFCHAIN | uAttributes;
				this->Pages->uAttribs[uPage] = (MEMORY_PAGE_ATTRIB_USED | MEMORY_PAGE_ATTRIB_ENDOFCHAIN | uAttributes);

				return ((byte *)Pages->uBaseAddress) + (uStartingPage * Pages->uSize);
			}
		}
		else {
			uContiguousPages = 0; //Found a used page, reset the contiguous count.
		}
	}

	return NULL; //Insufficient memory or contiguous Pages->
}

Free walks forward from the given page clearing flags until it hits ENDOFCHAIN, and ReAllocP either extends the chain in place (when the following pages are free) or allocates fresh space, copies, and frees the old block. A minimal driver:

#include <Windows.h>
#include <STDIO.h>
#include <STDLIB.h>

#include "CFileMemory.H"

int main(int argc, char *argv[])
{
	CFileMemory fileMem("C:\\memorymap.txt", true, 1024 * 1024 * 10);

	fileMem.Test();

	int freed = fileMem.FreeAll();

	return 0;
}
An unhandled error has occurred. Reload 🗙