3.10 Dynamic Memory Allocation and the Heap Segment


3.10 Dynamic Memory Allocation and the Heap Segment

Although static and automatic variables are all simple programs may need, more sophisticated programs need the ability to allocate and deallocate storage dynamically (at runtime) under program control. In the C language, you would use the malloc and free functions for this purpose. C++ provides the new and delete operators. Pascal uses new and dispose. Other languages provide comparable facilities. These memory allocation routines share a couple of things in common: They let the programmer request how many bytes of storage to allocate, they return a pointer to the newly allocated storage, and they provide a facility for returning the storage to the system so the system can reuse it in a future allocation call. As you've probably guessed, HLA also provides a set of routines in the HLA Standard Library that handle memory allocation and deallocation.

The HLA Standard Library malloc and free routines handle the memory allocation and deallocation chores (respectively). The malloc routine uses the following calling sequence:

 malloc( Number_of_Bytes_Requested ); 

The single parameter is a double word value specifying the number of bytes of storage you need. This procedure allocates storage in the heap segment in memory. The HLA malloc function locates an unused block of memory of the size you specify in the heap segment and marks the block as "in use" so that future calls to malloc will not allocate this same storage. After marking the block as "in use" the malloc routine returns a pointer to the first byte of this storage in the EAX register.

For many objects, you will know the number of bytes that you need in order to represent that object in memory. For example, if you wish to allocate storage for an uns32 variable, you could use the following call to the malloc routine:

 malloc( 4 ); 

Although you can specify a literal constant as this example suggests, it's generally a poor idea to do so when allocating storage for a specific data type. Instead, use the HLA built-in compile time function[16] @size to compute the size of some data type. The @size function uses the following syntax:

 @size( variable_or_type_name ) 

The @size function returns an unsigned integer constant that is the size of its parameter in bytes. So you should rewrite the previous call to malloc as follows:

 malloc( @size( uns32 )); 

This call will properly allocate a sufficient amount of storage for the specified object, regardless of its type. While it is unlikely that the number of bytes required by an uns32 object will ever change, this is not necessarily true for other data types, so you should always use @size rather than a literal constant in these calls.

Upon return from the malloc routine, the EAX register contains the address of the storage you have requested (see Figure 3-20 on the following page).

click to expand
Figure 3-20: Call to malloc Returns a Pointer in the EAX Register.

To access the storage malloc allocates you must use a register indirect addressing mode. The following code sequence demonstrates how to assign the value 1234 to the uns32 variable malloc creates:

 malloc( @size( uns32 )); mov( 1234, (type uns32 [eax])); 

Note the use of the type coercion operator. This is necessary in this example because anonymous variables don't have a type associated with them, and the constant 1234 could be a word or dword value. The type coercion operator eliminates the ambiguity.

The malloc routine may not always succeed. If there isn't a single contiguous block of free memory in the heap segment that is large enough to satisfy the request, then the malloc routine will raise an ex.MemoryAllocationFailure exception. If you do not provide a try..exception..endtry handler to deal with this situation, a memory allocation failure will cause your program to stop. Because most programs do not allocate massive amounts of dynamic storage using malloc, this exception rarely occurs. However, you should never assume that the memory allocation will always occur without error.

When you are done using a value that malloc allocates on the heap, you can release the storage (that is, mark it as "no longer in use") by calling the free procedure. The free routine requires a single parameter that must be an address returned by a previous call to malloc (that you have not already freed). The following code fragment demonstrates the nature of the malloc/free pairing:

      malloc( @size( uns32));           << use the storage pointed at by EAX >>           << Note: this code must not modify EAX >>      free( eax ); 

This code demonstrates a very important point: In order to properly free the storage that malloc allocates, you must preserve the value that malloc returns. There are several ways to do this if you need to use EAX for some other purpose; you could save the pointer value on the stack using push and pop instructions, or you could save EAX's value in a variable until you need to free it.

Storage you release is available for reuse by future calls to the malloc routine. The ability to allocate storage when you need it and then free the storage for other use when you are done with it improves the memory efficiency of your program. By deallocating storage once you are finished with it, your program can reuse that storage for other purposes, allowing your program to operate with less memory than it would if you statically allocated storage for the individual objects.

Several problems can occur when you use pointers. You should be aware of a few common errors that beginning programmers make when using dynamic storage allocation routines like malloc and free:

  • Mistake #1: Continuing to refer to storage after you free it. Once you return storage to the system via the call to free, you should no longer access that storage. Doing so may cause a protection fault or, worse yet, corrupt other data in your program without indicating an error.

  • Mistake #2: Calling free twice to release a single block of storage. Doing so may accidentally free some other storage that you did not intend to release or, worse yet, it may corrupt the system memory management tables.

The next chapter will discuss some additional problems you will typically encounter when dealing with dynamically allocated storage.

The examples thus far in this section have all allocated storage for a single unsigned 32-bit object. Obviously you can allocate storage for any data type using a call to malloc by simply specifying the size of that object as malloc's parameter. It is also possible to allocate storage for a sequence of contiguous objects in memory when calling malloc. For example, the following code will allocate storage for a sequence of eight characters:

 malloc( @size( char ) * 8 ); 

Note the use of the constant expression to compute the number of bytes required by an eight-character sequence. Because "@size(char)" always returns a constant value (one in this case), the compiler can compute the value of the expression "@size(char) * 8" without generating any extra machine instructions.

Calls to malloc always allocate multiple bytes of storage in contiguous memory locations. Hence the former call to malloc produces the sequence appearing in Figure 3-21.

click to expand
Figure 3-21: Allocating a Sequence of Eight-Character Objects Using Malloc.

To access these extra character values you use an offset from the base address (contained in EAX upon return from malloc). For example, "mov( ch, [eax + 2] );" stores the character found in CH into the third byte that malloc allocates. You can also use an addressing mode like "[EAX + EBX]" to step through each of the allocated objects under program control. For example, the following code will set all the characters in a block of 128 bytes to the NULL character (#0):

      malloc( 128 );      for( mov( 0, ebx ); ebx < 128; add( 1, ebx ) ) do           mov( 0, (type byte [eax+ebx]) );      endfor; 

The next chapter discusses composite data structures (including arrays) and describes additional ways to deal with blocks of memory.

You should note that a call to malloc will actually allocate slightly more memory than you request. For one thing, memory allocation requests are generally of some minimum size (often a power of 2 between 4 and 16, though this is OS dependent). Furthermore, malloc requests also require a few bytes of overhead for each request (generally around 8 to 16 bytes) to keep track of allocated and free blocks. Therefore, it is not efficient to allocate a large number of small objects with individual calls to malloc. The overhead for each allocation may be greater than the storage you actually use. Typically, you'll use malloc to allocate storage for arrays or large records (structures) rather than small objects.

[16]A compile time function is one that HLA evaluates during the compilation of your program rather than at runtime.




The Art of Assembly Language
The Art of Assembly Language
ISBN: 1593272073
EAN: 2147483647
Year: 2005
Pages: 246
Authors: Randall Hyde

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net