Measuring Fragmentation

As a program dynamically allocates memory, available memory is subdivided and fragmented into blocks - often leading to program failure. James examines how some popular compilers and memory managers handle fragmentation, then presents a method for quantifying the degree of fragmentation.


April 01, 1993
URL:http://www.drdobbs.com/database/measuring-fragmentation/184408979

Figure 2


APR93: MEASURING FRAGMENTATION

MEASURING FRAGMENTATION

Are malloc and free fragmenting your heap?

James Harrington

Jim is a consultant on memory-management issues and author of a C memory-management library. He may be reached at Library Technologies, P.O. Box 56031, Madison, WI 53705-9331, or on CompuServe at 71333,30.


As a program dynamically allocates memory, available memory is subdivided into blocks by an allocation function. In C, that function is malloc(). Usually, all allocated blocks wind up at contiguous addresses until some memory is freed. When some (but not all) blocks are freed, the result is usually fragmented memory--free memory not in a single contiguous block. In such cases, free memory is broken into less-optimally sized, smaller blocks, separated from each other by allocated blocks. Depending on the implementation of the allocation and free functions, adjacent freed blocks may not necessarily be combined into single larger free blocks; they may remain as separate free blocks--another manifestation of fragmentation.

When free memory becomes fragmented and an allocation request is made, the allocation function may return NULL ("not enough memory") even if enough total free memory is available, because no single contiguous free block is large enough to satisfy the memory request. Furthermore, if memory is allocated at the base of the higher of two adjacent free blocks, there's free memory below and above the allocated block. This free memory would have existed as a single larger free block if adjacent free blocks had been previously combined.

Fortunately, once you've determined that fragmentation is a problem, you can often avoid it. I'll present a method for quantifying the degree of fragmentation, and give code that implements this method for Borland C++ 3.1 and Microsoft C/C++ 7.0. The tests presented here were performed under DOS 5.0 on a 20-MHz 80386 with 8 Mbytes of RAM (although only the first 640K of RAM is relevant).

A Fragmentation Index

The heap is the expanse of memory that dynamic memory-allocation functions utilize as the pool of allocatable memory. All addresses returned by malloc() or other standard allocation functions point into the heap. The current configuration of your program's heap consists of the sizes and locations of allocated and free blocks at the particular moment in question. You can assign a value called the "fragmentation index" to any heap configuration according to the formula in Figure 1, which gives larger values as fragmentation increases. The size of the largest allocatable block is the largest number of bytes you can request from the allocation function without having it return NULL. Be aware that the fragmentation index applies to the memory configuration only at a particular moment in your program's execution. You can get the index a number of times as your program runs and see the progression of heap fragmentation as it occurs. You can use values of the index to determine which portions of code are causing the greatest fragmentation problems, and/or whether fragmentation is a problem at all. Using this formula requires you to access the entire memory configuration. (On some PCs or compilers, this isn't possible.)

Figure 1: Formula to calculate the fragmentation index. The index produces larger values as fragmentation increases.

                        breakup factor
fragmentation index = -----------------
                      large free factor

where

                    number of free blocks
breakup factor =  -------------------------
                  number of used blocks + 1

and

                     size of largest allocatable block
large free factor = -----------------------------------
                        total amount of free memory

Logic Behind the Formula

Several issues relate to the fragmentation index, the first of which is the number of free blocks. The ideal is to have only one free block, containing all free memory. The more individual free blocks there are, the worse memory is fragmented.

The number of used blocks is relevant because the more used blocks there are, the more free blocks could potentially be separating them. For example, if your heap contains 20 used blocks and 15 free blocks, there is a potential maximum of only 21 separate free blocks, assuming no adjacent free blocks are allowed. You have 15 free blocks, so free memory is broken into 72 percent as many pieces as are possible. Conversely, if your heap contains 2000 used blocks but still has only 15 free blocks, there could be as many as 2001 free blocks. Therefore, free memory is fragmented into only 0.7 percent as many pieces as it could be. The fragmentation index included here reflects this subtlety, and incorporates both the absolute amount of fragmentation present and a measure of how well the allocation/free implementation keeps fragmentation to a minimum.

The total amount of free memory is also relevant. If the largest allocatable block contains nearly all of free memory, the heap is nearly optimally configured, regardless of the number of small free blocks. On the other hand, if the largest allocatable block is only a small portion of total free memory, the heap is badly fragmented.

The size of the largest allocatable block is the focal point of any interest in fragmentation. The larger this block is, the less effect fragmentation has on your program. One element that is not factored into the index, however, is the size of blocks you need to allocate. Ideally, that would be a part of the formula, expressed relative to the size of the largest allocatable block. Unfortunately, that value is of less general use.

Interpreting Index Values

In considering the interpretation of index values, recognize that the index equation involves a compound fraction and that no fraction can have 0 as the denominator. Thus, if you have no free blocks, the index will give an undefined value. In such a case, the code presented here will return a fragmentation index of -1. It will also return -1 if the heap has been corrupted such that the index can't be properly calculated.

In normal situations, the index equation returns a value from 0 to some very large number. If there's exactly one free block in the heap (the ideal situation), the numerator is 0, and the fragmentation index is at its minimum value.

There is a maximum value for the index, but the exact value of that maximum depends on the implementation of the allocation and free functions and on the amount of memory available to the heap. For example, consider the Borland compiler's far-heap allocation function with an available heap of, say, 80 Kbytes. A maximally fragmented heap will always consist of alternating used and free blocks, which are of the absolute minimum size allowable by the allocator. With Borland C++, each block would have the minimum size of 16 bytes, and the size of the largest allocatable block would be 12 bytes. (Each block contains four bytes of overhead.) The corresponding value of the fragmentation index according to Figure 1 is the maximum of approximately 3331.

What sorts of fragmentation index values can you expect in a real program? Given an 80-Kbyte heap and Borland C++, assume there are 500 allocated blocks in the heap with 49 free blocks interspersed amongst the used blocks. Assume one free block above the highest used block, for a total of 50 free blocks. The total free memory is 20 Kbytes and the largest allocatable block is 5 Kbytes. The average size of the used blocks is 120 bytes, which is reasonable for some programs with many small blocks and a few large ones. The average size of the free blocks is 400 bytes. The value of the fragmentation index is 0.4. Whether the 0.4 value is high enough to represent a problem depends on the size of the block(s) you need to allocate relative to the size of the largest allocatable block (which is only one of the factors in the equation).

Using the Index

If your allocation function returns NULL, the fragmentation index has an excessively high value, and the block being allocated is not particularly large, then fragmentation is probably responsible for the NULL return.

If you determine that fragmentation rather than lack of memory is the problem, you have several options: implement or purchase a better malloc() and free(); allocate and free memory in a different order; subdivide large blocks and dole out smaller chunks of memory; or use static buffers for some blocks whose allocation leads to increased fragmentation. Use the fragmentation index to help locate the sections of code where fragmentation increases, and focus your memory-management efforts on blocks allocated or freed there.

Comparing Index Values

You might think that if fragmentation-index value A is twice that of value B, fragmentation in configuration A is twice that of configuration B. However, you can't directly compare the index values like this. For instance, imagine a heap configuration that contains exactly three free blocks of equal size. Now imagine that allocating and freeing several blocks results in each free block being bisected--there are now six free blocks, each half as large as before. Intuitively, you could say that the level of fragmentation has exactly doubled. However, two factors in the equation have been altered: increasing the number of free blocks and decreasing the size of the largest allocatable block. As a result, the original fragmentation index is multiplied by a factor of four rather than two.

To intuitively compare two indexes, each should be transformed into a relative fragmentation index using the equation in Figure 2. First, identify the smallest value from the set of fragmentation-index values to be compared. That value, which I'll call Z, is assigned a relative fragmentation index of 1.0. Determine the relative fragmentation indexes corresponding to other values according to the formula in Figure 2. Of course, you can choose an arbitrarily small, positive, nonzero raw index as a base "Z value," and compute all other relative fragmentation indexes against it.

Compiler-specific Computation

C source code for a function fragindex() that computes the raw fragmentation index I've described is provided electronically; see "Availability," page 5. This code is not specific to any memory manager. However, the functions external to the module must be written to be compatible with the particular allocator being used. Additional code provides for the external functions, which can be used with the Borland C++ and Microsoft C/C++ memory manager. This code generates results for the far heap, which is the default heap for far data-memory models. (malloc() always allocates from the default heap.) Code for the near heap would be very similar.

The total free memory in a large-model, Borland-compiled program consists of the return value of farcoreleft() plus the total sizes of all free blocks as reported by farheapwalk(), plus four bytes for every free block. The size of the largest allocatable block is the size of the largest free block reported through heapwalk(), or the return value of farcoreleft() minus 4, whichever is larger. The number of free blocks is the number of free blocks reported by heapwalk() plus 1, for the "coreleft" memory.

Collecting the required information for Microsoft's heap is more complicated, since malloc() allocates chunks of memory from MS-DOS and then subdivides these large allocated blocks as required. The heapwalk() function reports free memory within the DOS blocks allocated by malloc(), but does not report on any existing free DOS memory blocks (even though those memory blocks are part of the heap). Determining the total amount of free memory requires, first, determining and adding the sizes of all free DOS blocks, adding 16 bytes per DOS block for the arena headers, and adding to the sum the sizes of the free blocks reported by heapwalk() plus two bytes per free block. The size of the largest allocatable block is the size of the largest free block reported by heapwalk() or the size of the largest free DOS block (not including the bytes in the arena header), whichever is larger.

Another complication is that the Microsoft allocator does not combine adjacent free blocks, which heapwalk() reflects. Therefore, the fragmentation index is affected. However, Microsoft's malloc() will combine adjacent free blocks if it is required to be able to satisfy an allocation request. Thus, to determine the size of the largest allocatable block, add the sizes of adjacent free blocks, plus 2n (where n is the number of combined blocks minus 1) to account for per-block overhead. If you do this, adjacent free blocks should not be considered separate, and the number of free blocks may be correspondingly reduced. Additionally, free DOS blocks must be counted as separate free blocks.

Comparing Memory Managers

Given code that is able to compute fragmentation indexes for different memory managers, it's tempting to compare the memory managers to see how well they do at avoiding fragmentation. Do this with caution. For example, Borland's memory manager is able to use only the free memory immediately above the program block after loading. Microsoft's memory manager, on the other hand, can use any allocatable DOS block (even those that might be below the program block or in UMBs if they are linked in). Thus, the actual size of the heap may be different for the two allocators, which affects the total amount of free memory and presumably the size of the largest allocatable block. The size of the heap should be the same in each test run in order to fairly and directly compare how well the allocators avoid fragmentation.

Also, any test program must place the allocators under stress without forcing early failure of either allocator. One extreme would be a program that simply allocates 500 blocks of memory without freeing any. Clearly, any halfway-decent memory manager would yield a fragmentation index of 0 after this allocation/free pattern. At the other extreme would be a pattern of allocations and frees that quickly fragments the heap. When comparing different allocators, the fragmentation index must be measured with each allocator having gone through exactly the same pattern of allocations and frees, and excessive fragmentation can prevent that. Finally, the pattern of memory allocations and frees should be analogous to that of a real-world program. Thus, more small blocks should be allocated than large ones.

Listing One, page 92, presents a test program that may be used to test any memory allocator that doesn't fail before the call to fragindex(). (Some third-party allocators require initialization/cleanup calls that are not included, and may require a redefinition of the allocation function's name.) I used Listing One to compare the Borland and Microsoft allocators. The early call to setdos() is used to equalize the playing field by allocating all free DOS blocks that the Microsoft memory manager could use except the one large block immediately above the program block at startup. This avoids one uncontrolled program difference. In addition, the Borland modules rand.obj and n_lxmul.obj were linked into programs with both allocators so that the pattern of allocations and frees would be identical in both compiled versions. When the programs were run, no TSRs or other memory managers (such as QEMM or 386-MAX) were loaded. One difference between the two test programs was that the base of the heap (the top of DGROUP), and thus its size, was not identical for the two programs, since the compiled code size was different. However, the difference was small relative to the total amount of memory available in the heap, and should not have a great effect on the results. Table 1 shows the results of 7200 random allocations and frees. max size is the size of the largest allocatable block; nfree is the number of free blocks; rawidx is the raw fragmentation index; and relidx is the relative fragmentation index.

Table 1: Results from Listing One, which test the Borland and Microsoft memory allocators.

          max size  nfree  rawidx  relidx
------------------------------------------
Borland     156732   515    .6366    1.00
Microsoft    57105   745    2.180    1.89

Though the raw and relative fragmentation indexes for the allocators will be higher or lower, depending on the test program and the amount of memory available, the Borland allocator will always yield better results with respect to fragmentation, due to several factors. First, the Microsoft allocator does not combine adjacent free blocks as they are created, immediately leading to increased fragmentation and probably more with subsequent allocations. Also, the Microsoft allocator often doesn't reuse previously allocated and freed memory unless it has to. Additionally, the Microsoft allocator breaks DOS memory into chunks as part of the basic allocation algorithm, inherently creating some fragmentation. The Borland allocator behaves differently in all three areas. Periodic calls to Microsoft's _heap-min() function will reduce the Microsoft allocator's fragmentation index, but it will still be higher than Borland's.

The implementation of the allocation and free functions often greatly affects fragmentation. In an evaluation of seven commercially available C memory managers using the source code associated with this article, the raw fragmentation indexes ranged at termination from 0.1556 to 34.79, corresponding to relative fragmentation indexes from 1.0 to 3.9. These numbers demonstrate a tremendous range in effectiveness at avoiding fragmentation. The two best memory managers in that particular test used a best-fit allocation algorithm instead of the first-fit algorithm used by the compilers' malloc() functions. Using a best-fit algorithm alone does not guarantee good memory management, however, as another manager that purportedly used a best-fit algorithm gave the worst results of any of the seven. (Before condemning any memory manager out of hand, recall that a number of other factors should be considered when evaluating a memory manager.)

Summary

It's important to be aware of the negative effects fragmentation can have on your program. If it fails due to lack of memory, check whether reducing fragmentation will allow your program to run successfully without adding memory resources. The fragmentation index presented here should help you make that determination, and serve as a tool to help you manage your program's memory usage more effectively. Using an appropriate memory manager is always an important step in achieving good memory management in your program, but it is especially critical when memory resources are limited and fragmentation becomes a problem.



_MEASURING FRAGMENTATION_
by James Harrington


[LISTING ONE]


/* This module contains a generic function that calculates a fragmen-
   tation index. This index depends on the numbers of used and free
   block, the largest number of bytes that can currently be allocated
   in a contiguous block, and the total amount of free memory.

   Several functions declared as "extern" will be found in other
   modules, where each module calculates values as appropriate for
   a particular memory manager.
*/

#include <stdio.h>

double fragindex( void );
double getHeapSizeF( void );
double getBreakupF( void );
double getLargeFreeF( void );
double getWorstCase( void );

extern double getWorstCase( void );   /* In manager-specific module */
extern long getnfreeblks( void );   /* In manager-specific module */
extern long getnusedblks( void );   /* In manager-specific module */
extern long getlargest( void );     /* In manager-specific module */
extern long gettotalfree( void );

/*********************************************************************
*
* double fragindex( void )
*
* This procedure returns a relative value that reflects the degree of
* heap fragmentation. It is calculated as follows:
*
*      frag_index = BreakupFactor / LargeFreeFactor
*
*      BreakupFactor = number of free blocks-1 / number of used
*         blocks+1, where the free memory above utilized heap is
*         counted as a free block.
*      LargeFreeFactor = size of largest block that can be success-
*         fully allocated / total amount of unused memory,
*         including overhead.
*
* maximal fragmentation occurs when every other block is free, all
* blocks are of the minimal size, and all of memory has been filled
* with used:free block pairs.
*
* If adjacent free blocks are allowed to exist, maximal fragmentation
* occurs when every block is free (except perhaps for one, and not
* counting any blocks allocated by startup code), all blocks are of
* the minimal size, and all of memory has been filled with free
* blocks.
*
* Returns -1 in case of error condition - corrupt heap or setdos()
* not called in an MSC program. setdos() is required only if compar-
* ing with other memory managers.
*
*********************************************************************/

double fragindex( void )
{
   double breakupF, largeFreeF;

   breakupF = getBreakupF();
   largeFreeF = getLargeFreeF();

   if( breakupF<0 || largeFreeF<0 )
      return( -1 );

   if( !largeFreeF )
      return( 0 );
   else
      return ( breakupF/largeFreeF );
}

/*********************************************************************
*
* getBreakupF
*
* Returns the BreakupFactor for use in the fragindex function.
*
*********************************************************************/

double getBreakupF( void )
{
   long nfree, nused;

   nfree = getnfreeblks();
   nused = getnusedblks();

   if( nfree<=0 )
      return(nfree);

   return (double)(--nfree) / (double)(++nused);
}

/*********************************************************************
*
* getLargeFreeF
*
* Returns the LargeFreeFactor for use in the fragindex function.
*
* Returns -1 if error in heapwalk, 0 if is no free memory
*
*********************************************************************/

double getLargeFreeF( void )
{
   double sizelargest;

   sizelargest = (double)getlargest();

   if( sizelargest < 0 )
      return( -1 );

   if( !sizelargest )
      return( 0 );
   else
      return (sizelargest / (double)gettotalfree());
}






[LISTING TWO]


/* Provides Borland-specific versions of functions called by fragindex() */

/* Assumes no UMBs, etc., are available to malloc() */

#include <alloc.h>
#include <stdlib.h>
#include <stdio.h>

long gettotalfree( void );
long getnusedblks( void );
long getnfreeblks( void );
long getlargest( void );
void setdos( void );

/*********************************************************************
*
* getlargest( void );
*
* Returns a number of bytes, the size of the largest block of
* contiguous free memory that can be successfully allocated from the
* heap with farmalloc(), malloc(), or _halloc().
*
*********************************************************************/

long getlargest( void )
{
   struct farheapinfo hinfo;
   long size = 0;

   hinfo.ptr = NULL;

   while( heapwalk( &hinfo ) == _HEAPOK ){

      if( !hinfo.in_use )
         size = max( size, hinfo.size );
   }

   size = max( size, farcoreleft()-4 );

   return( size );
}

/*********************************************************************
*
* getnfreeblks( void );
*
* Returns the number of free blocks in the heap. Doesn't count memory
* above utilized heap.
*
* Returns -1 if heap is corrupt
*
*********************************************************************/

long getnfreeblks( void )
{
   struct farheapinfo hinfo;
   register int count = 0;

   if( heapcheck() < 0 )
      return( -1 );

   hinfo.ptr = NULL;

   while( heapwalk( &hinfo ) == _HEAPOK ){

      if( !hinfo.in_use )
         count++;
   }
   /* ret count+1 to count coreleft() memory */
   return (long) ++count;
}

/*********************************************************************
*
* getnusedblks( void );
*
* Returns the number of allocated blocks in the heap.
*
*********************************************************************/

long getnusedblks( void )
{
   struct farheapinfo hinfo;
   register int count = 0;

   hinfo.ptr = NULL;

   while( heapwalk( &hinfo ) == _HEAPOK ){

      if( hinfo.in_use )
         count++;
   }

   return (long) count;
}

/*********************************************************************
*
* gettotalfree( void );
*
* Returns the total amount of free memory.
*
*********************************************************************/

long gettotalfree( void )
{
   struct farheapinfo hinfo;
   long size=0;

   hinfo.ptr = NULL;

   while( heapwalk( &hinfo ) != _HEAPEND ){

      if( !hinfo.in_use )
         size+=(hinfo.size+4);
   }
   size += farcoreleft();

   return (long)size;
}


/*********************************************************************
*
* setdos( void );
*
* For sake of compatibility with fragmic.c
*
*********************************************************************/

void setdos( void )
{
}





[LISTING THREE]


/* Provides Microsoft C-specific versions of functions used in
   calculating a fragmentation index.                         */

/* Assumes no UMBs, etc., are available to malloc() */

#include <malloc.h>
#include <stdlib.h>
#include <stdio.h>
#include <dos.h>

#define LOWSEGS 100
      /* signed int, must be > 0 */
      /* represents the max number of
         free DOS blocks expected at
         time of program startup */
#define NDOS 200
      /* signed int, must be > 0 */
      /* affected by value of
         _amblksize variable - smaller
         val means larger NDOS req'd */
      /* represents max number of
         free DOS blocks
         expected            */

int nsegs, _callflag;
unsigned _far dosseg_array[ LOWSEGS ]; /* saves segs for findfrag() */
unsigned _far seg_array[ NDOS ];       /* saves segs for setdos()   */

void limitheap( void );
void freesegs( void );
long countdos( void );
long bigdos( void );
long dossize( void );
long gettotalfree( void );
long getnusedblks( void );
long getnfreeblks( void );
long getlargest( void );
void setdos( void );
void freedos( int count );

/* Note: _dos_allocmem() is a compiler runtime library function that
   allocates a block of memory directly from MS-DOS through int 21h
   fxn. 48h. You request a number of paragraphs (blocks of 16 bytes).
   Requesting 0xffff paragraphs will result in an error return, with
   the size of the largest allocable block, in paragraphs, being
   returned in the variable whose address is passed as the second
   parameter to _dos_allocmem(). _dos_freemem() frees the blocks
   allocated through _dos_allocmem(), via int 21h fxn 49h.
*/

/*********************************************************************
*
* getlargest( void );
*
* Returns a number of bytes, the size of the largest block of contigu-
* ous free memory that can be successfully allocated from the heap
* with fmalloc(), malloc() (in far data memory models), or _halloc().
*
* Returns -1 if error
*
*********************************************************************/

long getlargest( void )
{
   struct _heapinfo hinfo;
   int herr;
   long maxsize = 0;
   long tsize = 0;
   char first = 0;
   unsigned seg = 0;

   maxsize = bigdos();

   /* Get largest free */

   hinfo._pentry = NULL;

   while( (herr=_heapwalk( &hinfo )) != _HEAPEND && herr==_HEAPOK){

      /* if is a used block... */
      if( hinfo._useflag ){

         first = 0;

         seg = (unsigned)(((unsigned long)hinfo._pentry)>>16);
      }

      /* but if is a free block... */
      else{

         /* if last block was a used block */
         if( !first ){

            tsize = hinfo._size;

            /* If switch DOS blocks */
            if( (unsigned)(((unsigned long)hinfo._pentry)>>16)
                                             != seg )
               seg =
                  (unsigned)(((unsigned long)hinfo._pentry)>>16);

            maxsize = max( maxsize, tsize );

            first=1;
         }

         /* if last block was a free block too */
         else{

            /* If switch DOS blocks */
            if( (unsigned)(((unsigned long)hinfo._pentry)>>16)
                                           != seg ) {
               seg =
                 (unsigned)(((unsigned long)hinfo._pentry)>>16);
               tsize = hinfo._size;
            }
            else
               tsize += (hinfo._size+2);

            maxsize = max( maxsize, tsize );
         }
      }
   }

   return( herr==_HEAPEND?maxsize:-1 );
}

/*********************************************************************
*
* long bigdos( void )
*
* Returns the size of the largest block of available DOS memory.
*
*********************************************************************/

long bigdos( void )
{
   unsigned size;

   _dos_allocmem( 0xffff, &size );

   return( (long)(size-1) * 16L );   /* Size of largest allocable
                                             block */
}


/*********************************************************************
*
* getnfreeblks( void );
*
* Returns the number of free blocks in the heap, and assumes adjacent
* free blocks in the same DOS block can be combined.
*
* Returns -1 in case of error
*
*********************************************************************/

long getnfreeblks( void )
{
   struct _heapinfo hinfo;
   int herr;
   unsigned seg;
   long count = 0;
   char first = 0;

   /* error if didn't call setdos() */
   if( !_callflag )
      return( -1 );

   hinfo._pentry = NULL;

   while( (herr=_heapwalk( &hinfo )) != _HEAPEND && herr==_HEAPOK){

      /* if is a used block... */
      if( hinfo._useflag ){

         first = 0;

         seg = (unsigned)(((unsigned long)hinfo._pentry)>>16);
      }

      /* but if is a free block... */
      else{

         /* if last block was a used block */
         if( !first ){

            count++;

            /* If switch DOS blocks */
            if( (unsigned)(((unsigned long)hinfo._pentry)>>16)
                                              != seg )
               seg =
                (unsigned)(((unsigned long)hinfo._pentry)>>16);
         }

         /* if last block was a free block too */
         else{

            /* If switch DOS blocks */
            if( (unsigned)(((unsigned long)hinfo._pentry)>>16)
                                              != seg ){
               seg =
                  (unsigned)(((unsigned long)hinfo._pentry)>>16);
               count++;
            }
         }
      }
   }

   return( herr==_HEAPEND?(count+countdos()):-1 );
}

/*********************************************************************
*
* int countdos( void )
*
* Counts the number of free DOS blocks, up to NDOS blocks
*
*********************************************************************/

long countdos( void )
{
   int count;

   for( count=0; count<NDOS; count++ ){

      _dos_allocmem( 0xffff, &seg_array[count] );

      if( seg_array[count] )
         _dos_allocmem( seg_array[count], &seg_array[count] );
      else
         break;
   }

   /* count is the number of allocated blocks */

   freedos(count);

   if( count == NDOS ){
      printf("\nToo many DOS blocks; increase value of NDOS.");
      exit(0);
   }

   return count;
}

/*********************************************************************
*
* getnusedblks( void );
*
* Returns the number of allocated blocks in the heap.
*
*********************************************************************/

long getnusedblks( void )
{
   struct _heapinfo hinfo;
   long count = 0;

   hinfo._pentry = NULL;

   while( _heapwalk( &hinfo ) != _HEAPEND ){

      if( hinfo._useflag )
         count++;
   }
   return count;
}

/*********************************************************************
*
* gettotalfree( void );
*
* Returns the total amount of free memory.
*
*********************************************************************/

long gettotalfree( void )
{
   struct _heapinfo hinfo;
   long size=0;

   hinfo._pentry = NULL;

   while( _heapwalk( &hinfo ) != _HEAPEND ){

      if( !hinfo._useflag )
         size+=(hinfo._size+2);
   }
   return size + dossize();
}

/*********************************************************************
*
* dossize( void )
*
* Counts the number of bytes in free DOS blocks, up to NDOS DOS
* blocks. Includes 16 bytes overhead for each block.
*
*********************************************************************/
long dossize( void )
{
   unsigned seg_array[NDOS];
   int count;
   long size = 0;

   for( count=0; count<NDOS; count++ ){

      _dos_allocmem( 0xffff, &seg_array[count] );

      if( seg_array[count] ){
         size += ((long)seg_array[count]+1)*16L;
         _dos_allocmem( seg_array[count], &seg_array[count] );
      }
      else
         break;
   }

   /* count is the number of allocated blocks */

   freedos(count);

   if( count == NDOS ){
      printf("\nToo many DOS blocks; increase value of NDOS.");
      exit(0);
   }

   return( size );
}

/*********************************************************************
*
* void freedos( int count )
*
* Frees segments stored in seg_array[].
*
* Parm "count" is number of stored segments.
*
*********************************************************************/

void freedos( int count )
{
   int i;

   if( !count )
      return;

   for( i=0; i<count; i++ )
      _dos_freemem( seg_array[i] );
}

/*********************************************************************
*
* setdos( void );
*
*********************************************************************/

void setdos( void )
{
   atexit( freesegs );
   limitheap();
   _callflag++;
}

/*********************************************************************
*
* limitheap()
*
* This function allocates all the DOS blocks besides the one
* immediately above the program block. Assumes that the one above the
* program block is the biggest free DOS block.
*
*********************************************************************/
void limitheap(void)
{
   /* nsegs = 0 at start */

   while( nsegs<LOWSEGS ){

      _dos_allocmem( 0xffff, &dosseg_array[nsegs] );

      if( dosseg_array[nsegs] ){
         _dos_allocmem( dosseg_array[nsegs],&dosseg_array[nsegs] );
         nsegs++;
      }
      else
         break;
   }

   if( nsegs == LOWSEGS ){

      _dos_freemem( dosseg_array[0] );

      printf("\nToo many free DOS blocks at start; ");
      printf("\nincrease value of of LOWSEGS.");
      exit(0);
   }

   if( nsegs )
      _dos_freemem( dosseg_array[0] );
}

void freesegs(void)
{
   int i;

   if( !_callflag )
      printf("\nerror: didn't call setdos()");
   else{
      if( nsegs>1 ){

         for( i=1; i<nsegs; i++ )
            _dos_freemem( dosseg_array[i] );
      }
   }
}












Copyright © 1993, Dr. Dobb's Journal

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.