/*----------------------------------------------------------------------
  membug.c
  Demonstrate MSC malloc() large size problem

  Description

       membug.c demonstrates a problem that occurs when Microsoft C,
       version 5.10 is used to allocate and free large blocks of
       memory.

       If this program is compiled and run, you will find 
       that the first list will have significantly more memory
       allocated to it. The second list will only have 1 to 2
       elements allocated to it, depending on your memory layout.

       The basic problem is that MSC never deallocates a DOS
       allocated memory block, even if the memory call is about to
       fail. Thus, the first list causes the MSC runtime to allocate
       memory in 48K blocks. When the first list is freed, the
       48K blocks remain. Then, when the second list is allocated,
       there are only 2 blocks that DOS can carve the 60K blocks
       from: the default memory segment and the last DOS memory block.
       The default memory segment is 64K, so we should always get
       an allocation from it. The last memory block can be expanded
       by DOS to fit the 60K request if your memory layout will allow
       it.

       Note that if you reverse the order of memory requests, both
       will return the same number of memory blocks because the 48K
       requests will fit in the 60K blocks.

  Compilation

       Compilation is under Microsoft C, verison 5.1 using the command:

               c1 /W3 /AL membug.c
 
  Execution

       Execution of the program should use the command line:

               membug > membug.out
*/

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

/* Local definitions */
/* ----------------- */
#define FIRST_ALLOC_SIZE 	48000
#define SECOND_ALLOC_SIZE	60000

/* Memory allocation list structure */
/* -------------------------------- */
typedef struct mb                       /* Memory list node             */
        {                               /* ---------------------------- */
        struct mb *     mb_next ;       /* Pointer to next block	*/
        char            mb_data ;       /* Start of data area           */
                                        /* Actual data area size is     */
                                        /* determined by runtime        */
                                        /* malloc() argument            */
        } MEM_BLOCK ;

/* Pointer conversion macros */
/* ------------------------- */
#define FARPTR_SEG(a) ((int) (((unsigned long) (a)) >> 16))
#define FARPTR_OFF(a) ((int) ((long) (a)))
#define MAKE_FARPTR(seg,off) ((void far *) ((((long) (seg)) << 16) + (off)))

/* Function prototypes */
/* ------------------- */
void	main() ;
void	DOS_Mem_Display(char *) ;

/*------------------------------------------------------------------------

  main
  Entry point for MSC dynamic memory test

  Usage

     void
     main()
 
  Parameters

     None
 
  Description

     main() is the entry point for the Microsoft C dynamic memory
     test. The function allocates a list of FIRST_ALLOC_SIZE 
     elements, frees the first list, allocates a second
     list of SECOND_ALLOC_SIZE, and frees the second list. The
     statistics printed out are the total bytes allocated by each
     allocation and a dump of the DOS memory list after each
     allocation/free.

  Notes

     None
*/

void


main()
{
MEM_BLOCK * list ;
MEM_BLOCK * p ;
long	first_size ;
long	second_size ;

/* Allocate list using first allocation size */
/* ----------------------------------------- */
list = NULL ;
first_size = 0 ;
while ((p = (MEM_BLOCK *) malloc(FIRST_ALLOC_SIZE)) != NULL)
	{
	p->mb_next = list ;
	list = p ;
	first_size += FIRST_ALLOC_SIZE ;
	}

/* Print first allocation results */
/* ------------------------------ */
printf("***** First allocation - %ld *****\n\n", first_size) ;
DOS_Mem_Display("After first allocation\n") ;

/* Free first list */
/* --------------- */
while (list != NULL)
	{
	p = list ;
	list = list->mb_next ;
	free(p) ;
	}

DOS_Mem_Display("After first free\n") ;

/* Allocate list using second allocation size */
/* ------------------------------------------- */
list = NULL ;
second_size = 0 ;
while ((p = (MEM_BLOCK *) malloc(SECOND_ALLOC_SIZE)) != NULL)
	{
	p->mb_next = list ;
	list = p ;
	second_size += SECOND_ALLOC_SIZE ;
	}

/* Print second allocation results */
/* ------------------------------- */
printf("***** Second allocation - %ld *****\n\n", second_size) ;
DOS_Mem_Display("After second allocation\n") ;

/* Free second list */
/* ---------------- */
while (list != NULL)
	{
	p = list ;
	list = list->mb_next ;
	free (p) ;
	}

DOS_Mem_Display("After second free\n") ;
}

/*--------------------------------------------------------------

DOS_Mem_Display

Display DOS memory allocation list

Usage

       void
       DOS_Mem_Display(ttl)
       char * ttl ;

Parameters

       ttl     Title string

Description
       
       DOS_Mem_Display() displays the contents
       of the DOS memory allocation list.

Notes

       This function uses an undocumented DOS call to get the
       address of the first memory block. Actually, the memory
       list format is also undocumented...

       The function uses the Microsoft C intdosx() function.
       Not highly portable to any other compiler...
       Certainly not portable to another operating system.
*/

void 
DOS_Mem_Display (ttl)
char * ttl;
{
unsigned int far *	ip ;
unsigned char far * 	p ;
unsigned char far *	prg ;
int			psp_seg ;
int			blk_paras ;
int			idx ;
long 			size ;
long			total ;
char 			str[80] ;
union REGS		regs ;
struct SREGS		sregs ;

printf("DOS memory list - %s\n", ttl) ;

/* Use undocumented DOS call to get first mem block */
/* ------------------------------------------------ */
regs.h.ah = 0x52 ;
intdosx(&regs, &regs, &sregs) ;
ip = ((unsigned int far *) MAKE_FARPTR (sregs.es, regs.x.bx)) - 1 ;
p = MAKE_FARPTR(*ip, 0) ;

idx = 0 ;
total = 0 ;
for (;;) 
       {
       psp_seg = *(p+1) + ((*(p+2)) << 8) ;
       blk_paras = *(p+3) + ((*(p+4)) << 8) ;
       size = ((long) blk_paras) << 4 ;
       if (psp_seg == 0)
               {
               prg = (unsigned char far *) "(free)" ;
               total += size ;
               }
       else
               {
               ip = (unsigned int far *) MAKE_FARPTR(psp_seg, 0x2c) ;
               prg = MAKE_FARPTR(*ip, 0) ;
               while (*prg != '\0')
                       {
                       prg += strlen((char *) prg) + 1 ;
                       }
               prg += 3 ;
               }
       sprintf(str, "%5d %9ld %p", idx++, size, p) ;

       printf("%s\t%s\n", str, prg) ;

       if (*p == 'z')
               {
               break ;
               }

       p = MAKE_FARPTR(FARPTR_SEG(p) + blk_paras + 1, 0) ;
       }

sprintf(str, "Total Free: %ld", total) ;
printf("%s\n\n", str) ;
}

/*---------------------------------------------------------------------*/





