[comp.sources.misc] Replacement malloc for Microsoft C 4.0

allbery@ncoast.UUCP (06/16/87)

Here is a replacement for the standard malloc, realloc, calloc, free,
sbrk, and brk for Microsoft C 4.0.

It was written to satisfy the need for a 'debugging malloc' in the MSC4.0
environment on the PC.

It can be used in production code with impunity, though it isn't as
efficient space-wise as the standard set of routines.

The core routines have been tested in all memory models - but as noted,
there will need to be some work on the debug code to make it function
properly in other than small code, small data.

-----------------------------CUT HERE----------------------------------------
/*
** Alloc.c - replacement memory allocator for Microsoft C 4.0
**
** malloc, free, calloc, realloc
** Implemented in a totally paranoid fashion - if anyone walks on the
** heap, you know about it immediately.
** If you define TEST, you get some debug code compiled in.
** If you define MALLOCTEST, you get a little malloc debugger.
** If you undefine NOSTDIO, you get standard I/O rather than my oddball
** stuff for i/o.
**
** Alas, the debugging code here assumes small code, small data - though
** the malloc'er itself works fine in larger models.  The things that
** would have to change is 1) some of the code in the memtest routine.
** 2) the code for returnaddress. 3) The calls to brkctl.
**
** This is provided with no warranty at all by:
** Kent Williams
** 722 Rundell
** Iowa City, IA 52240
** {...cwruecmp}!ncoast!kent
**
*/

#define NOSTDIO
#define NULL 0

/*
** low level breaker.
** It is documented in Xenix documentation as
** char far *brkctl(command,increment,ptr)
**	int command; long increment; char far *ptr;
**
** where command is one of:
** #define BR_ARGSEG	0001	specified segment
** #define BR_NEWSEG	0002	new segment
** #define BR_IMPSEG	0003	implied segment - last data segment
** #define BR_FREESEG	0004	free the specified segment
** #define BR_HUGE      0100    do the specified command in the huge context.
**
** In small model, command should always be BR_ARGSEG, and
** ptr should be the address of something in the data segment.
** In large model, you're on your own, though I think you could probably
** call with BR_ARGSEG and the default data segment the first time, and
** BR_IMPSEG on all subsequent calls.
**
** Returns either a far ptr to allocated memory, or -1 if there's an error.
*/
extern char far *brkctl(int,long,char far *);

/*
** Mystery variable that keeps the library malloc from getting pulled in.
** It SEEMS to be just the offset of the start of the heap ... so that's
** what I set it up to.  NOTE : I figured this all out by disassembling the
** dynamic memory allocation routines - If they change the library, all
** bets are off.
*/
char *_asegds = NULL;

/*
** I don't use stdio.  If you do, you should undefine NOSTDIO
*/
#ifdef NOSTDIO
	extern char *getline(char *);
#	define stdin 0
#	define stdout 1
#	define stderr 2
#else
#	include <stdio.h>
#	define getline(x)	gets(x)
#endif

/* constants */
#define MAGIC_BUSY 0x1234
#define MAGIC_FREE 0x8765

#ifdef TEST
/*
** types used for 'return-stamping' memory allocation blocks.
*/
typedef void (*funcptr)(void);
extern funcptr returnaddress(void);
#endif

/* structures */
typedef struct _qqq
{
	struct _qqq *next,*last;
	unsigned size;
	unsigned magic;
#ifdef TEST
	/*
	** stamp memory blocks with the address of who created/destroyed them.
	*/
	funcptr returnadd;
#endif	/* TEST */
} mblock;

#ifdef TEST
/*
** If you undef NOSTDIO above, then we assume you're not running in
** my environment, and need the following routine.
*/
#ifdef NOSTDIO
/*
** funky subtrefuge to pick up the return address
** essentialy a replacement for:
	public	_returnaddress
_returnaddress	proc	near
	mov	ax,2[bp]
	sub	ax,3
	ret
_returnaddress	endp
**
*/
funcptr returnaddress(x)
unsigned x;
{
	register unsigned *xp;
	union { unsigned up; funcptr fp; } rval;
	/*
	**	pick up previous frame pointer.
	**  This only works for small code, small data, though
	**  it shouldn't be too hard to figure out other models.
	*/
	xp = &x;	/* x is at 4[bp] */
	xp -= 2;	/* old frame pointer is at 0[bp] */
	xp = (unsigned *)(*xp);
	xp += 1;	/* return address is at 2[oldbp] */
	rval.up = *xp - 3;
	return rval.fp;
}
#endif	/* NOSTDIO */

/*
** mark a block with a return address.  Useful when 'envelope'
** functions are used to call the actual allocation routines.
*/
void mark_block(ptr,address)
register mblock *ptr;
funcptr address;
{
	(--ptr)->returnadd = address;
}
#endif	/* TEST */

/*
** QUANTUM is the minimum size blob requested from brkctl.
*/
#define QUANTUM ((long)((4096+sizeof(mblock)-1)/sizeof(mblock)))

/*
** queueing macros
*/
#define enqueue(q,i)	q_insert((q)->last,i)
#define dequeue(q)		(q_remove( (q) ->next))

#define q_empty(q) 		((q)->next==(q)&&(q)->last==(q))

/*
** externals
*/
#include <ctype.h>
#include <string.h>

/*
** q_insert - insert item in the queue designated by head
** returns : Nothing
*/
static void q_insert(head,item)
register mblock *head,*item;
{
	item->last = head;
	item->next = head->next;
	head->next->last = item;
	head->next = item;
}

/*
** q_remove - remove an item from a queue.
** returns NULL if queue is empty
*/
static mblock *q_remove(item)
register mblock *item;
{
	if(item->next == item && item->last == item)
		return (mblock *)NULL;
	item->next->last = item->last;
	item->last->next = item->next;
	return item;
}

mblock arena = { NULL,NULL,0,0 };


/* 
** check_malloc - make sure everything is kosher with a particular
** memory block
*/
int check_malloc(item)
register mblock *item;
{
	if(item->next->last != item)
		return -1;
	if(item->last->next != item)
		return -1;
	if(item->magic != MAGIC_BUSY && item->magic != MAGIC_FREE)
		return -1;
	return 0;
}


/*
** free an allocated memory block
*/
int free(item)
register mblock *item;
{
#ifdef TEST
	int reason;
	static char *reasons[] = {
		"Not initialized",
		"Bad pointer or chain corrupted",
		"Already freed",
	};
#endif
	/* check for initialization */
	if(arena.next == NULL) {
		arena.next = arena.last = &arena;
		arena.magic = MAGIC_BUSY;
		arena.size = 0;
		/*
		** if there isn't anything allocated, freeing anything is an error.
		*/
#ifdef TEST
	{
		reason = 0;
		goto bad_free;
	}
#else
		return -1;
#endif
	}
	
	/* cheat by decrementing the pointer to access the magic block */
	--item;

	/* if this is a gypsy block, return error */
	if(check_malloc(item))
#ifdef TEST
	{
		reason = 1;
		goto bad_free;
	}
#else
		return -1;
#endif
	if(item->magic != MAGIC_BUSY)
#ifdef TEST
	{
		reason = 2;
	bad_free:
		fprintf(stderr,"Bad call to free from %04x, reason = %s\n",
			returnaddress(),reasons[reason]);
		myexit(1);
	}
#else
		return -1;
#endif

	/* if the item being freed isn't an empty list (i.e. not
	** linked into the allocation list), then try and merge it
	** with contiguous blocks
	*/
	if(item->next != item) {
		/* mark block as free (invariant) */
		item->magic = MAGIC_FREE;
		/* check to see if next block is free, and contiguous with the
		** current block
		*/
		if((item->next->magic == MAGIC_FREE) &&
			(item->next == (item + item->size))) {
			/* add in size of next block */
			item->size += item->next->size;
			/* remove the next block from the queue */
			(void)q_remove(item->next);
		}
		/* check to see if last block is free and contiguous with the current
		** block
		*/
		if((item->last->magic == MAGIC_FREE) &&
			(item == (item->last + item->last->size))) {
			item->last->size += item->size;
			(void)q_remove(item);
		}
#ifdef TEST
		item->returnadd = returnaddress();
#endif
		return 0;
	}
	/* we are freeing something that's never been allocated */
	item->magic = MAGIC_FREE;
	enqueue(&arena,item);	/* just enqueue the block */
	return 0;
}

void *malloc(size)
unsigned size;
{
	register mblock *current;
	long lmsize;
	/* check for initialization */
	if(arena.next == NULL) {
		arena.next = arena.last = &arena;
		arena.magic = MAGIC_BUSY;
		arena.size = 0;
	}
	/* adjust size to be an even number of mblocks
	        bump to next even mblock          round           add one */
	size = ( (size + (sizeof(mblock) - 1) ) / sizeof(mblock) ) + 1;
	/* search down free list to find a block */
	for (current = arena.next; current != &arena; current = current->next)
		if(current->magic == MAGIC_FREE) {
			if(current->size < size)
				continue;
			malloc_again:
			if(current->size == size || current->size == size + 1) {
				current->magic = MAGIC_BUSY;
#ifdef TEST
				current->returnadd = returnaddress();
#endif
				return (void *)(current+1);
			}
			/* current->size must be > size, split block */
			else {
				register mblock *new_guy;
				/* compute new fragment's address */
				new_guy = current + size;
				/* compute his size */
				new_guy->size = current->size - size;
				/* make him free */
				new_guy->magic = MAGIC_FREE;
				/* insert him in the arena just after current */
				q_insert(current,new_guy);
				current->magic = MAGIC_BUSY;
				current->size = size;
#ifdef TEST
				current->returnadd = returnaddress();
#endif
				return (void *)(current+1);
			}
		}
	/* we have to malloc up a blob */
	if(size+1 < QUANTUM)
		lmsize = (long)QUANTUM * (long)sizeof(mblock);
	else
		lmsize = (long)size * (long)sizeof(mblock);
	/*
	** call brkctl to get memory.
	*/
	if((mblock *)-1 == 
		(current = (mblock *)brkctl(1,lmsize,((char far *)&arena)))) {
		
		return NULL;
	}
	/*
	** _asegds seems to be a pointer to the first block allocated in
	** the heap.
	*/
	if(_asegds == NULL)
		_asegds = (char *)current;
	/* make the new block an empty list */
	current->next = current->last = current;
	current->size = (unsigned)(lmsize/sizeof(mblock));
	current->magic = MAGIC_BUSY;
	(void)free(current + 1);
	goto malloc_again;
	/*lint -unreachable */
}

void *calloc(size,num)
unsigned size,num;
{
	register char *rval;
	long msize = size *num;
	if(msize >= 65536L)
		return NULL;
	if(NULL != (rval = malloc((unsigned)msize)))
		memset(rval,0,msize);
	return rval;
}

void *realloc(ptr,size)
mblock *ptr; unsigned size;
{
	/* save the 'real' size for later */
	unsigned bytesize = size;
	unsigned freespace;	/* size of the new block */
	mblock *new_guy;	/* pointer to following fragment */
	/* check for initialization */
	if(arena.next == NULL) {
		arena.next = arena.last = &arena;
		arena.magic = MAGIC_BUSY;
		arena.size = 0;
		/*
		** if there isn't anything allocated, freeing anything is an error.
		*/
		return NULL;
	}
	/* look at the memory block */
	--ptr;
	/* adjust size to be an even number of mblocks
	        bump to next even mblock          round           add one */
	size = ( (size + (sizeof(mblock) - 1) ) / sizeof(mblock) ) + 1;
	if(check_malloc(ptr)) {
#ifdef TEST
		fprintf(stderr,"realloc : bogus pointer %x from %x\n",ptr+1,
			returnaddress());
#endif
		return NULL;
	}
	/* none of this reallocing free blocks bullshit.  It might work, but
	** why encourage people in that kind of behavior?
	*/
	if(ptr->magic != MAGIC_BUSY)
		return NULL;
	/* pathological case - realloc of same size */
	if(size == ptr->size) {
#ifdef TEST
				ptr->returnadd = returnaddress();
#endif
		return (void *)(ptr+1);
	}
	/* are we shrinking ? */
	if(ptr->size > size)
		goto split_block; /* split the current block into two chunks */

	/* try and grow it in place */
	if((ptr->next->magic == MAGIC_FREE) &&	/* is it free ? */
		(ptr->next == (ptr + ptr->size)) && /* is it contiguous ? */
		((ptr->size + ptr->next->size) >= size)) /* is it big enough ? */ {
		/* join with the next block */
		freespace = ptr->size + ptr->next->size;
		/* get rid of the next block */
		(void)q_remove(ptr->next);
		ptr->size = freespace;	/* put in the new size */
		/* if there isn't enough room for a fragment, don't break the chunk */
		if(freespace == size || freespace == size+1) {
#ifdef TEST
				ptr->returnadd = returnaddress();
#endif
			return (void *)(ptr+1);
		}
	split_block:
		/* otherwise, split the block, a la malloc */
		new_guy = ptr + size;/* compute new fragment's address */
		/* compute his size */
		new_guy->size = ptr->size - size;
		/* make him free */
		new_guy->magic = MAGIC_FREE;
		/* insert him in the arena just after ptr */
		q_insert(ptr,new_guy);
		ptr->size = size;
#ifdef TEST
		ptr->returnadd = returnaddress();
#endif
		return (void *)(ptr+1);
	} else
	/* easiest case to implement - malloc new block, copy then free
	** the old block
	*/ {
		++ptr;	/* undo the decrement to look at memory block stuff */
		if(NULL == (new_guy = (mblock *)malloc(bytesize))) {
#ifdef TEST
			fprintf(stderr,"realloc: out of memory\n");
#endif
			return NULL;
		}
		memcpy((char *)new_guy,(char *)ptr,bytesize);
		(void)free(ptr);
#ifdef TEST
		new_guy[-1].returnadd = returnaddress();
#endif
 		return (void *)new_guy;
	}
}

#ifdef TEST
void free_all()
{
	mblock *current;
	restart:
	for (current = arena.next; current != &arena; current = current->next)
		if(current->magic == MAGIC_BUSY) {
			if(0 != free(current+1)) {
				printf("error freeing %p\n",(char far *)current);
				return;
			}
			goto restart;
		}
	while (NULL != (current = dequeue(&arena)))
		free((char *)current);
}
#endif

/*
** implement a near sbrk on top of brkctl.
*/
char *sbrk(x)
int x;
{
	return (char *)brkctl(1,(long)x,((char far *)&arena));
}

/*
** implement a brk on top of brkctl.
*/
int brk(p)
char *p;
{
	char *current = sbrk(0);
	return (int)brkctl(1,(long)(p-current),((char far *)&arena));
}

#ifdef MALLOCTEST
void print_chain()
{
	mblock *current;
	for (current = arena.next; current != &arena; current = current->next) {
		printf("[%04x] %04x (%04x) : next = %04x last = %04x size = %u %s\n",
			current->returnadd,
			current,
			(&current[1]),current->next,
			current->last,
			current->size * sizeof(mblock),
			current->magic == MAGIC_FREE ? "FREE" : "BUSY");
	}
}

#include <signal.h>
#include <setjmp.h>
static jmp_buf flubber;
static int sig_catcher()
{
	signal(SIGINT,sig_catcher);
	longjmp(flubber,-1);
}

void memtest()
{
	static char buf[256];
	unsigned siz;
	char *ptr;
	signal(SIGINT,sig_catcher);
	for(;;) {
		setjmp(flubber);
		fputs("> ",stdout);
		if(NULL == getline(buf))
			break;
		switch(toupper(buf[0])) {
		case 'Q':
			return 0;
		case 'M':
			if(1 == sscanf(buf+1,"%u",&siz))
				printf("malloc(%u) = %04x\n",siz,malloc(siz));
			else
				printf("bad malloc string : %s\n",buf);
			continue;
		case 'F':
			if(1 == sscanf(buf+1,
#ifndef LARGE_DATA
						"%x"
#else
						"%04x"
#endif
				,&ptr))
				printf("free(%04x) = %d\n",ptr,free(ptr));
			else
				printf("bad free string : %s\n",buf);
			continue;
		case 'P':
			print_chain();
			continue;
		case 'A':
			free_all();
			continue;
		case 'H':
			printf("M = malloc <siz>\nF = free <ptr>\n");
			printf("P = print malloc chain\nA = free everything\n");
			printf("H = help\nQ = quit\nR = realloc <ptr> <size>\n");
			printf("S = printf <ptr>\n");
			continue;
		case 'S':
			if(sscanf(buf+1,"%x",&ptr) == 1)
				printf("string = %s\n",ptr);
			else
				printf("bad pointer : %s\n",buf);
			continue;
		case 'R':
			if(2 == sscanf(buf+1,"%x %u",&ptr,&siz))
				printf("realloc(%04x,%u) = %04x\n",
					ptr,siz,realloc(ptr,siz));
			else
				printf("bad realloc string : %s\n",buf);
			continue;
		}
	}
}
#endif