Subject: lib/3027: malloc(3) fragments memory unnecessarily
To: None <gnats-bugs@gnats.netbsd.org>
From: None <gord@profitpress.com>
List: netbsd-bugs
Date: 12/14/1996 03:52:04
>Number:         3027
>Category:       lib
>Synopsis:       malloc(3) fragments memory unnecessarily
>Confidential:   no
>Severity:       serious
>Priority:       medium
>Responsible:    lib-bug-people (Library Bug People)
>State:          open
>Class:          sw-bug
>Submitter-Id:   net
>Arrival-Date:   Sat Dec 14 03:05:02 1996
>Last-Modified:
>Originator:     Gordon Matzigkeit
>Organization:
The Profit Press
>Release:        1.2
>Environment:
Out-of-the box NetBSD/i386 1.2

System: NetBSD burger.profitpress.com 1.2 NetBSD 1.2 (GENERIC) #3: Fri Oct 18 02:08:09 PDT 1996 gord@burger.profitpress.com:/usr/src/sys/arch/i386/compile/GENERIC i386


>Description:
Using free(3) to release malloc'ed memory does not return the system
to its original state.  The behaviour of malloc(3) is quite erratic,
depending on which order mallocs and free are performed in.
>How-To-Repeat:
The following program can be used to investigate the problem:

-- malloctest.c ------------------------------------------------------
#include <stdio.h>
#include <stdlib.h>

struct node
{
  struct node *next;
  void *data;
} *list;


static char *program_name;

static void
usage (int status)
{
  printf ("Usage: %s [increase|decrease|constant] [chunksize]\n", program_name);
  exit (status);
}


int
main (int argc, char **argv)
{
  int total, chunk, max_per_chunk;
  char mode = 'c';
  struct node *n;

  program_name = argv[0];
  if (argc > 3)
    usage (1);

  if (argc > 1)
    mode = *argv[1];

  switch (mode)
    {
    case 'i':
    case 'd':
    case 'c':
	break;

    default:
      fprintf (stderr, "invalid mode `%s'\n", argv[1]);
      usage (1);
    }


  chunk = 1;
  if (argc > 2)
    {
      char *p = argv[2];
      while (*p && isdigit (*p))
	p ++;
      if (*p)
	{
	  fprintf (stderr, "chunk size must be a positive integer\n");
	  usage (1);
	}
      chunk = atoi (argv[2]);
    }

  max_per_chunk = 0;
  while (chunk > 0 && (list = malloc (sizeof (struct node))))
    {
      list->next = NULL;
      n = list;
      total = 0;

      while (total < max_per_chunk || max_per_chunk == 0)
	{
	  /* Allocate CHUNK MB of memory in each loop. */
	  n->data = malloc (chunk * 1024 * 1024);
	  if (!n->data)
	    break;

	  total += chunk;
	  n->next = malloc (sizeof (struct node));
	  if (!n->next)
	    break;

	  n = n->next;
	  n->data = NULL;
	  n->next = NULL;
	}

      if (!total)
	{
	  fprintf (stderr, "Could not malloc any %d MB chunks\n", chunk);
	  exit (1);
	}

      fprintf (stderr, "Got %d MB (in %d MB chunks)\n", total, chunk);

      while (list)
	{
	  /* Free all the memory that we allocated. */
	  n = list;
	  list = n->next;

	  if (n->data)
	    {
	      free (n->data);
	      total -= chunk;
	    }
	  free (n);
	}

      if (total)
	fprintf (stderr, "Did not release %d MB\n", total);

      /* Maybe modify our chunk size. */
      switch (mode)
	{
	case 'i':
	  chunk ++;
	  break;

	case 'd':
	  chunk --;
	  break;
	}
    }

  exit (0);
}
----------------------------------------------------------------------

I created two binaries: `malloc' and `gmalloc' from the above program.
`malloc' is linked against the NetBSD 1.2 system malloc, and `gmalloc'
is linked against the GNU C library's malloc.

Here are samples of the programs' output:

burger:~/src/malloc$ ./malloc
Got 15 MB (in 1 MB chunks)
Got 15 MB (in 1 MB chunks)
Got 15 MB (in 1 MB chunks)
Got 15 MB (in 1 MB chunks)
[runs forever]
burger:~/src/malloc$ ./gmalloc
Got 15 MB (in 1 MB chunks)
Got 15 MB (in 1 MB chunks)
Got 15 MB (in 1 MB chunks)
Got 15 MB (in 1 MB chunks)
[runs forever]
burger:~/src/malloc$ ./malloc increase
Got 15 MB (in 1 MB chunks)
Could not malloc any 2 MB chunks
burger:~/src/malloc$ ./gmalloc increase
Got 15 MB (in 1 MB chunks)
Got 14 MB (in 2 MB chunks)
Got 12 MB (in 3 MB chunks)
Got 12 MB (in 4 MB chunks)
Got 10 MB (in 5 MB chunks)
Got 12 MB (in 6 MB chunks)
Got 14 MB (in 7 MB chunks)
Got 8 MB (in 8 MB chunks)
Could not malloc any 9 MB chunks
burger:~/src/malloc$ ./malloc decrease 8
Got 8 MB (in 8 MB chunks)
Got 7 MB (in 7 MB chunks)
Got 6 MB (in 6 MB chunks)
Got 5 MB (in 5 MB chunks)
Got 4 MB (in 4 MB chunks)
Got 3 MB (in 3 MB chunks)
Got 2 MB (in 2 MB chunks)
Got 1 MB (in 1 MB chunks)
burger:~/src/malloc$ ./gmalloc decrease 8
Got 8 MB (in 8 MB chunks)
Got 14 MB (in 7 MB chunks)
Got 12 MB (in 6 MB chunks)
Got 10 MB (in 5 MB chunks)
Got 12 MB (in 4 MB chunks)
Got 12 MB (in 3 MB chunks)
Got 14 MB (in 2 MB chunks)
Got 15 MB (in 1 MB chunks)
burger:~/src/malloc$

>Fix:
As a workaround, use GNU malloc instead of the system malloc.
>Audit-Trail:
>Unformatted: