Huge Global Memory Blocks

The dwSize parameter in GlobalAlloc is a 32-bit DWORD (double word), large enough in theory to allocate a 4-gigabyte block of memory. Although you obviously won't be able to get a block quite that large, it appears that you can still allocate a block of memory larger than 64 KB. Yes, you can, but you have to be careful. Beginning with version 4 of the Microsoft C Compiler, the huge keyword was implemented for defining variables that are larger than 64 KB. A huge pointer is 32 bits, just like a far pointer. However, the Microsoft C Compiler assumes that a far pointer addresses only a 64-KB range and will never run past the end of the segment. With a huge pointer, the compiler generates code that checks for segment overrun and does appropriate segment arithmetic on the pointer.

The phrase ”segment arithmetic“ should have triggered a bell in your head! I mentioned earlier that you should not perform segment arithmetic in your Windows programs because it violates rules of protected mode. Fortunately, the Microsoft C 6 compiler and Windows work together to perform different segment arithmetic depending on whether the program is running in real mode or protected mode. In real mode, jumping from the end of one 64-KB segment to the beginning of another segment requires adding 0x1000. In protected mode, the selectors are allocated so that 8 is added for the segment jump. (Note: Don't rely on this number; it may change under future versions.)

When you use GlobalAlloc to allocate memory greater than 64 KB, you must cast the pointer returned from GlobalLock into a huge pointer and save it as a huge pointer. For instance, this code allocates a 128-KB memory block and locks it:

GLOBALHANDLE hGlobalMemory ;

char huge *lpGlobalMemory ;

[other program lines]

hGlobalMemory = GlobalAlloc (GMEM_MOVEABLE, 0x20000L) ;

[other program lines]

lpGlobalMemory = (char huge *) GlobalLock (hGlobalMemory) ;

Every function that manipulates this huge pointer must be aware that the pointer is huge. If a function that is passed a huge pointer believes that the pointer is a simple far pointer, the Microsoft C Compiler will not generate any segment arithmetic when you manipulate the pointer. For this reason, you should not pass a huge pointer to most of the standard C library functions (the C 6 manuals list functions that support huge arrays) or to any of the Windows functions unless you know that the function will not be referencing the pointer past the end of a segment.

That's one problem with huge pointers. Another problem is the possibility that a single data item referenced by the pointer may straddle two segments. With a huge pointer to character data, this is never a problem, because each character is a byte. The offset address that GlobalLock returns is always 0, so the huge pointer can also safely reference arrays of all the standard data types (char, int, short, long, float, and double).

If you use a huge pointer to an array of structures, you will have no problems if the size of the structure is a power of 2 (such as 2, 4, 8, 16, and so forth). That guarantees that no single structure will straddle two segments. If the size of the structure is not a power of 2, then you are bound by two restrictions:

The data block allocated with GlobalAlloc cannot be larger than 128 KB.

The offset address returned from GlobalLock must be adjusted so that a structure does not straddle two segments.

The first rule is actually implied by the second rule. If the initial offset address is adjusted so that an element of the structure does not straddle the first and second segments, it will straddle the second and third segments.

This explanation requires an example. Let's say you want a huge memory block to hold an array of 15,000 structures, where each structure requires 6 bytes. You can use typedef statements for this structure and a far pointer to the structure:

#typedef struct

{

int element1 ;

long element2 ;

}

MYSTRUCT ;

#typedef MYSTRUCT huge *LPMYSTRUCT ;

In your program you can define a variable for the far pointer to the structure:

GLOBALHANDLE hGlobalMemory ;

LPMYSTRUCT lpMyStruct ;

[other program lines]

hGlobalMemory = GlobalAlloc (GHND, 15001 * sizeof (MYSTRUCT)) ;

lpMyStruct = (LPMYSTRUCT) ((65536L % sizeof (MYSTRUCT)) +

GlobalLock (hMem)) ;

The pointer returned from GlobalLock will have an offset address of 0. You must increase that so that a single structure does not straddle the two segments. The adjustment value is the remainder of 65,536 divided by the size of the structure. (In this case, the adjustment value is 4.) Because you have a little waste here, GlobalAlloc allocates one more structure than is really needed.