Many applications will be able to satisfy their memory needs using the standard allocation functions (GlobalAlloc, LocalAlloc , malloc, etc). However, the additional functionality of the Virtual functions makes it useful in some situations. One useful feature is the ability to reserve a contiguous block of address space from which pages can be committed as needed. This allows you to create dynamic data structures that can grow to the limits of the reserved region without committing pages until they are needed.
VirtualAlloc allocates pages of memory that are aligned on page boundaries. You can reserve or commit one or more free pages, or you can commit one or more previously reserved pages. Reserved pages have no physical storage, and committed pages are backed by storage that is visible only to the allocating process. Reserved pages are always allocated with a protection status of PAGE_NOACCESS; while committed pages may be allocated with read/write, read only, or no access. When pages are committed, backing storage is allocated in the paging file, but each page is initialized and loaded into physical memory only at the first attempt to read or write that page. The storage for committed pages will be released automatically when the process terminates. To free storage (MEM_DECOMMIT) or to release reserved pages (MEM_RELEASE) prior to termination, you would use VirtualFree as follows:
Release | Reserved or committed pages can be released only by freeing the entire block that was initially reserved by VirtualAlloc. The state of all pages in the block must be the same (either all committed or all reserved). If all of the pages in the block are committed, you can specify MEM_DECOMMIT | MEM_RELEASE to release them. If only part of the pages in the block are committed, these pages must first be decommitted before the entire block can be released. Once released, the pages are free and inaccessible. |
Decommit | Committed pages can be decommitted to release their storage so it available for other uses and other processes. Any page or block of pages that was committed by VirtualAlloc can be decommitted. Decommitting changes the state of the page to reserved, unless the page was both decommitted and released, in which case the state of the page is free. |
The following code fragments illustrates the use of VirtualAlloc and VirtualFree in reserving and committing as needed memory for a dynamic array. The first time VirtualAlloc is called, it is used to reserve a block of pages. In this case, NULL is passed as the base address parameter to allow the kernel to determine the location of the block. When VirtualAlloc is called to commit a page from this reserved region, the address parameter specifies the base address of the newly committed page. The example uses the try-except structured exception handling syntax to commit additional pages when a page fault exception occurs. The except block is executed only if an exception occurs while executing the try block. As an alternative to this dynamic allocation, the process could have simply committed the entire region instead of only reserving it, in which case, the committed pages would not be initialized or loaded until they are accessed. But this would reduce the total storage available to this and other processes. Notice that in freeing the pages, the committed pages are decommitted first, and then the entire region is released.
#define PAGELIMIT 80
#define PAGESIZE 0x1000
int pages = 0;
char *page_alloc(char *base) {
char *pg;
base += pages*PAGESIZE;
pg = VirtualAlloc(base, PAGESIZE, MEM_COMMIT, PAGE_READWRITE);
assert(pg);
pages++;
return pg;
}
BOOL free_pages(char *base) {
BOOL bResult;
// first decommit the committed pages
bResult = VirtualFree(base, pages*PAGESIZE, MEM_DECOMMIT);
// then release the entire buffer
if (bResult)
bResult = VirtualFree(base, 0, MEM_RELEASE);
return bResult;
}
main() {
char *base, *newpage;
BOOL bRes;
// reserve pages
base = (char *) VirtualAlloc(NULL, PAGELIMIT*PAGESIZE,
MEM_RESERVE, PAGE_NOACCESS);
assert (base);
// commit one page
newpage = page_alloc(base);
// use structured exception handling when accessing the pages
while (1) {
try {
// read or write to buffer that begins at base
base[5000] = 'a';
break;
}
except (EXCEPTION_EXECUTE_HANDLER) {
// if page fault occurs,
// commit another page and try it again
if (pages < PAGELIMIT) {
newpage = page_alloc(base);
}
else {
/* reserved pages used up: time to panic */
}
}
}
bRes = free_pages(base);
}
VirtualQuery returns information about a region of pages beginning at any specified address in the address space of a process. The region is bounded by the specified address rounded down to the nearest page boundary, and extends through all following pages with the same state (free, reserved, committed) and protection (read/write, read only, or no access). The information returned includes the state, protection, size in bytes, and type (shared or private). In the example above, this information could have been used to determine the number of committed pages that needed to be decommitted before being released.
VirtualProtect allows you to modify the access protection of any committed page in the address space of a process. This is typically used with pages allocated by VirtualAlloc , but will work with pages committed by any of the other allocation functions. Remember, however, that VirtualProtect changes the protection of entire pages, and that pointers returned by the other functions are not necessarily aligned on page boundaries. The following code fragment uses VirtualQuery to determine the number of pages committed in the previous example, and then uses VirtualProtect to change their protection to read only:
MEMORY_BASIC_INFORMATION MemInfo;
DWORD buflen;
char *base;
BOOL bResult;
DWORD dwOldProtect;
buflen = VirtualQuery(base, &MemInfo,
sizeof(MEMORY_BASIC_INFORMATION));
if (MemInfo.State == MEM_COMMIT)
bResult = VirtualProtect(base, MemInfo.RegionSize,
PAGE_READONLY, &dwOldProtect);