Arthur Shipman is a computer consultant working in the upstate New York area. He has a B.A. in mathematics from Mt. St. Mary College. He writes in Assembler, BASIC, and C on MS-DOS machines. You can contact him at P.O. Box 390, Westbrookville, NY 12785.
When you copy a record from a file to memory, allocating memory for the fields of the record as an array of strings avoids multiple calls to malloc and reduces allocation bookkeeping. However, you must build a more complex data structure, in contrast to allocating memory for a single string. Allocating an array of pointers to a set of strings, plus the strings themselves, involves a hierarchy of memory allocation. The routine CreateRecord in Listing 1 handles such complex allocation.
CreateRecord allocates memory for any array of strings whose maximum lengths are known in advance. The operation of the function is best explained in database terms: records, fields, and field lengths. CreateRecord takes three parameters: an integer nfields that gives the number of fields in a database record, an integer array of field lengths flen[], and an initialization character InitString. The initialization character is typically '\0' or SPACE. CreateRecord returns a pointer to an array of initialized fields. Thus, with little more than a set of field sizes, this function can create empty records for data entry or for input from a file.
CreateRecord performs a single memory allocation and returns one pointer to the allocated memory. (See Figure 1. ) The pointer points to an array of initialized pointers to char. These point in turn to a set of strings located in the allocated block following the pointer array. Each string is itself initialized to the InitString character value.
Simple Allocation
Figure 2 illustrates a simple allocation of memory where malloc assigns a pointer to a portion of available memory. Such an allocation might be coded
P = malloc(size);where P represents a pointer declared in your code. The boxes in the figure represent the allocated memory, and the arrow represents P receiving the address of the start of the memory block. This kind of allocation is inadequate, however, when creating an array of pointers similar to P. Only P is initialized to a certain value. None of the pointers in the array that P accesses is initialized.To use the pointers P[i] to designate strings, you must assign each its own portion of memory. The for loop
for(i=0; i<limit; i++) P[i] = malloc(size[i]);calls malloc for each pointer P[i]. Counting the original call, creating N fields requires N+1 calls to malloc. The overhead of function calls and allocation bookkeeping now begins to mount. Figure 3 illustrates the ungainly process.Note also that you must call free N+1 times to release the memory when it is no longer needed. Forgetting to free P[i], and simply freeing the original pointer P releases only one of the N+1 allocations. The rest remain allocated, and their memory becomes inaccessible.
Multiple Array Allocation
A substantially improved scheme sums the field lengths, and calls malloc only twice: once to assign the array of pointers to P, and once to allocate the space to which these pointers will point.However, avoiding the individual calls to malloc skirts the assignment of addresses to the pointer array and the allocation of space for strings. Initializing these pointers must still be handled, even if not in conjunction with calls to malloc.
This approach still lacks finesse. If P points to a single database record, a single call to free should release the memory. However, since malloc was called twice to create the record, you must call free twice in its disposal.
CreateRecord overcomes these problems by calculating the total memory required for the array of pointers plus the fields that these pointers will reference. A single malloc call allocates the required memory in a single block (Figure 1) . CreateRecord then divides this space between the pointers and the referenced fields. It assigns the field addresses to the pointer array and initializes the string space. Finally, CreateRecord returns a single pointer with which you can later free the entire allocation block.
CreateRecord has several advatages over the default allocation functions of standard C for complex allocation:
- CreateRecord efficiently creates arrays of strings often needed in C.
- The single call to malloc keeps both function call overhead and memory allocation overhead to a minimum.
- The return value is a pointer to an array of pointers that CreateRecord automatically initialized. Each points to a string whose length is predetermined by the caller.
- The strings can be initialized to any character value.
- The single memory allocation keeps the array of pointers with the character arrays they reference, in a single block of memory. While not necessary for proper operation of pointer arrays, this organization helps clarify what is happening in memory.
- Since malloc is called only once, deallocating the entire allocated block requires just one call to free.
Conclusion
A single allocation of memory is a straightforward act. But allocating an array of pointers, coupled with assigning memory to those array elements, is significantly more complex. CreateRecord minimizes the information required to perform such an allocation, and handles the process with a minimum of overhead. It produces an array that can be controlled and destroyed using a single pointer. In sum, it relieves the complexity involved in creating multiple character arrays.