Allocating Local Memory

I've been stressing the importance of using moveable (and, if possible, discardable) global memory blocks. With local memory you have the same options, but the guidelines are more relaxed. Whether you use fixed or moveable memory blocks within your local heap is up to you. Because your entire data segment is moveable (as it will be if you use the small or medium model), what you do inside your data segment doesn't affect other applications.

In fact, Windows makes it easier to use local memory if the blocks are fixed. The question to ask is: Can my local heap be smaller if I use moveable blocks instead of fixed blocks? If you use local memory a lot, and the life spans of the memory blocks overlap each other, then the answer to that question may be yes. If you use local memory allocations solely for short-lived memory, there's no reason to make the blocks moveable.

The local memory functions are similar to the global memory functions. Instead of GlobalAlloc, GlobalLock, GlobalUnlock, and GlobalFree, you use LocalAlloc, LocalLock, LocalUnlock, and LocalFree. Instead of identifiers that begin with GMEM, you use identifiers that begin with LMEM. The only real differences are these: The memory size passed to LocalAlloc is a WORD (unsigned integer) rather than a DWORD, and the pointer returned from LocalLock is a near pointer rather than a far pointer.

This is the syntax of LocalAlloc:

hLocalMemory = LocalAlloc (wFlags, wSize) ;

The wSize parameter is large enough to accommodate a requested size of 65,536 bytes, but you won't get a local block that large, because the data segment also includes your program's stack and static variables.

The wFlags parameter can first specify the attributes of the block:

LMEM_FIXED—Memory is fixed. (This is the default if wFlags is 0.)

LMEM_MOVEABLE—Memory is moveable.

LMEM_DISCARDABLE—Memory is discardable. This option should be used only with LMEM_MOVEABLE.

The LMEM_ZEROINIT flag zeroes out the memory block.

These two flags are equivalent to the similar flags for GlobalAlloc:

LMEM_NOCOMPACT—Windows will neither compact nor discard memory in the local heap when attempting to allocate the block.

LMEM_NODISCARD—Windows will not discard discardable memory in the local heap when attempting to allocate the block. Windows may still compact memory by moving moveable blocks.

WINDOWS.H also includes two shorthand flags for local memory allocations. The flag LHND (which stands for ”local handle“) is defined as:

LMEM_MOVEABLE | LMEM_ZEROINIT

The flag LPTR (”local pointer“) is defined as:

LMEM_FIXED | LMEM_ZEROINIT

If Windows cannot find enough memory in the local heap to allocate the block, it will attempt to expand the local heap by enlarging the size of the entire data segment. (Remember that the local heap is always at the top of the automatic data segment.) Windows may even move the data segment to another location in memory if that will provide the space it needs to expand the local heap. When LocalAlloc returns, your data segment may have been moved. (If this makes you nervous, check the section below entitled ”Locking Your Own Data Segment.“) The HEAPSIZE specification in the module definition (.DEF) file is really a minimum value for the heap.

If, after all this, Windows still cannot find enough memory in the local heap to allocate the memory block, the handle returned from LocalAlloc will be NULL. If you use local memory allocation only for small, short-lived memory blocks, you probably don't need to check the handle for a NULL value. (Alternatively, you might want to check the value during program development but not in the final version of the program.) If you do a lot of random local memory allocation with blocks of various sizes and different life spans, then you'll have to implement some kind of error processing.

LocalLock turns the local memory handle into a near pointer and then locks the block. LocalUnlock unlocks the block and invalidates the pointer. LocalFree frees the memory block and invalidates the handle.

Here's an example of using local memory to define the window class structure during program initialization:

LOCALHANDLE hLocalMemory ;

NPWNDCLASS npwndclass ;

[other program lines]

if (!hPrevInstance)

{

hLocalMemory = LocalAlloc (LHND, sizeof (WNDCLASS)) ;

npwndclass = (NPWNDCLASS) LocalLock (hLocalMemory) ;

npwndclass->style = CS_HREDRAW | CS_VREDRAW ;

npwndclass->lpfnWndProc = WndProc ;

npwndclass->cbClsExtra = 0 ;

npwndclass->cbWndExtra = 0 ;

npwndclass->hInstance = hInstance ;

npwndclass->hIcon = LoadIcon (NULL, IDI_APPLICATION) ;

npwndclass->hCursor = LoadCursor (NULL, IDC_ARROW) ;

npwndclass->hbrBackground = GetStockObject (WHITE_BRUSH) ;

npwndclass->lpszMenuName = NULL ;

npwndclass->lpszClassName = szAppName ;

RegisterClass (npwndclass) ;

LocalUnlock (hLocalMemory) ;

LocalFree (hLocalMemory) ;

}

The size of the memory block passed to LocalAlloc is the size of the WNDCLASS structure. LocalLock always returns a near pointer regardless of the memory model, because it allocates memory from the local heap in the program's automatic data segment. In this example, the pointer to type char that LocalLock returns is cast into a pointer to a WNDCLASS structure. The -> notation is used to reference the elements of a structure based on a pointer to the structure. In the RegisterClass call, we don't use the address (&) operator because npwndclass is already a pointer. Note also that the use of LHND initializes the block of memory to 0. All variables in the structure that take a 0 or NULL value need not be explicitly assigned.