/*
* Memory Allocator for Microsoft Windows.
* Only allows blocks of up to 64K minus overhead to be allocated.
*
* This code is based on the GNU malloc.c included with GNU emacs and is
* subject to the copyright restrictions listed therein. The original of
* malloc.c is included with this distribution. (I hope this approach is
* in accord with the license.)
*/
#ifdef _Windows
#include <mem.h>
extern char *NewSegment(void);
extern void FreeSegment(char *);
extern void abort(void);
extern int Exiting;
static void init_blocks(void);
static unsigned int find_block_index(unsigned long);
static void morecore(int);
char *Lmalloc(unsigned long);
char *Lrealloc(char *, unsigned long);
void Lfree(char *);
#else
#include <string.h>
#endif
/*
* nextf[i] is the pointer to the next free block of size bsize[i]. The
* smallest allocatable block is 8 bytes. The overhead information will
* go in the first long of the block, and the returned pointer will point
* to the second.
*/
#define ISALLOC ((char) 0xf7) /* magic byte that implies allocation */
#define ISFREE ((char) 0x54) /* magic byte that implies free block */
/* this is for error checking only */
/* What we have is a flag indicating whether memory is allocated, an index
in nextf[], and a size field; to realloc() memory we copy size bytes.
Since we are not allowing allocations of more than 64K, a short size
field is adequate for holding the actual size of any block. The size
field is only valid when the block is allocated.
*/
struct mhead {
char mh_alloc; /* ISALLOC or ISFREE */
unsigned char mh_index; /* index in nextf[] */
unsigned short mh_size; /* size */
};
/* Segments are connected in a linked list. The header is 16 bytes long and
a trailer of 16 bytes is used. This should hopefully insure that far
pointers to reasonable-sized structs will not wrap when incremented or
decremented
*/
#define SEGSIZE 0x010000L /* 64K */
#define SEGMAX ((int) (SEGSIZE - 32)) /* 64K - header - trailer */
struct shead {
struct shead *sh_next; /* pointer to next segment in list */
unsigned short sh_start; /* offset to first unallocated byte in segment */
unsigned short sh_free; /* amount of free space in segment */
long pad1, pad2; /* padding up to 16 byte boundary */
};
static struct shead *segs = 0;
/* BCC 4.0 seems to call malloc from its startup code (as do earlier */
/* versions). But under 4.0 an error occurs on exit if I do GlobalFree */
/* on the first segment that is created for this initial malloc. My best */
/* guess is that the cleanup code in BCC 4.0 calls GlobalFree directly */
/* instead of free, resulting in trying to free a block already free'd. */
/* So I record the first segment and don't free it in the cleanup code. */
/* This whole grossness should vanish with Win32s. */
static struct shead *firstseg = 0;
/* Access free-list pointer of a block. It is stored at block + 4.
This is not a field in the mhead structure because we want
sizeof (struct mhead) to describe the overhead for when the block
is in use, and we do not want the free-list pointer to count in that.
*/
#define CHAIN(a) \
(*(struct mhead **) (sizeof (char *) + (char *) (a)))
/* nextf[i] is free list of blocks of size bsize[i]. */
#define MAXINDEX 156
static struct mhead *nextf[MAXINDEX];
/* bsize[i] is the size of blocks of category i.
The shift up by one in bsize allows the range test in realloc to work.
(This range test is no longer used -- it was wrong) */
static unsigned int blocksizes[MAXINDEX + 1] = {0};
unsigned int *bsize = blocksizes + 1;
/* Function to call to issue a warning. */
#ifdef _Windows
static void (*warnfunction) (char *);
#else
static void (*warnfunction) ();
#endif
#define WARNING(s) if (warnfunction) (*warnfunction)(s)
/* initialize the block size array */
static void init_blocks(void)
{
register unsigned int i, j, k, m, n;
for (m = 8, i = 0, k = 0; i < 9; i++)
for (j = 0, n = m / 4; j < 4; j++, k++, m += n)
bsize[k] = m;
for (; k < MAXINDEX - 1; k++, m+= n)
bsize[k] = m;
m = SEGMAX;
bsize[k] = m;
}
/* Figure out which nextf[] area to use for a given size */
static unsigned int find_block_index(n)
register unsigned long n;
{
register long m;
register unsigned int i, j;
n += sizeof(struct mhead); /* add 4-byte header */
for (i = 0, m = 8; n > m && i < 9; i++, m <<= 1);
j = (unsigned int) (((n - 1) >> i) - 4);
return (i ? 1 + j + ((i - 1) << 2) : 0);
}
/* Initialize the allocator. */
void
malloc_init (warnfun)
void (*warnfun) ();
{
warnfunction = warnfun;
}
/* Clean up before shutdown */
void
malloc_cleanup()
{
struct shead *s;
while ((s = segs) != 0 && s != firstseg) {
segs = s->sh_next;
FreeSegment((char *) s);
}
}
static void
morecore (nu) /* ask system for more memory */
register int nu; /* size index to get more of */
{
register char *cp;
register unsigned int siz;
register struct shead *s, *nexts;
static int inited = 0;
if (! inited) {
init_blocks();
inited = 1;
}
/* check that nu is in range */
if (nu < 0 || nu >= MAXINDEX) {
WARNING("Can't allocate a block that large");
return;
}
/* calculate the size we need */
siz = bsize[nu];
/* see if there is a segment with enough space */
for (s = 0, nexts = segs; nexts; nexts = nexts->sh_next) {
if (siz <= nexts->sh_free) {
s = nexts;
break;
}
}
/* if we did not find one, get and initialize a new segment */
if (! s) {
s = (struct shead *) NewSegment();
if (! s) return;
s->sh_next = segs;
segs = s;
s->sh_free = SEGMAX;
s->sh_start = 16;
if (! firstseg) firstseg = segs;
}
/* double check the size */
if (!s || s->sh_free < siz) return;
/* get a new block from the segment and adjust the segment header */
cp = ((char *) s) + s->sh_start;
s->sh_start += siz;
s->sh_free -= siz;
/* save and initialize the new header */
nextf[nu] = (struct mhead *) cp;
((struct mhead *) cp) -> mh_alloc = ISFREE;
((struct mhead *) cp) -> mh_index = nu;
CHAIN ((struct mhead *) cp) = 0;
}
char *
Lmalloc (n) /* get a block */
unsigned long n;
{
register struct mhead *p;
register unsigned int nunits;
/* reject if requested block is too large */
if (n > SEGMAX - sizeof(struct mhead)) {
WARNING("Can't allocate a block that large");
return (0);
}
nunits = find_block_index(n);
/* reject if too large -- should not happen with earlier test */
if (nunits >= MAXINDEX) {
WARNING("Can't allocate a block that large");
return (0);
}
/* If there are no blocks of the appropriate size, go get some */
/* COULD SPLIT UP A LARGER BLOCK HERE ... ACT */
if (nextf[nunits] == 0)
morecore (nunits);
/* Get one block off the list, and set the new list head */
if ((p = nextf[nunits]) == 0)
return 0;
nextf[nunits] = CHAIN (p);
/* Check for free block clobbered */
/* If not for this check, we would gobble a clobbered free chain ptr */
/* and bomb out on the NEXT allocate of this size block */
if (p -> mh_alloc != ISFREE || p -> mh_index != nunits)
abort ();
/* Fill in the header info */
p -> mh_alloc = ISALLOC;
p -> mh_size = (unsigned short) n;
return (char *) (p + 1);
}
void Lfree (mem)
char *mem;
{
register struct mhead *p;
#ifdef _Windows
if (Exiting) return;
#endif /* _Windows */
{
register char *ap = mem;
if (ap == 0)
return;
p = (struct mhead *) ap - 1;
if (p -> mh_alloc != ISALLOC)
abort ();
}
{
register unsigned int nunits = p -> mh_index;
p -> mh_alloc = ISFREE;
/* Put this block on the free list. */
CHAIN (p) = nextf[nunits];
nextf[nunits] = p;
}
}
char *
Lrealloc (mem, n)
char *mem;
register unsigned long n;
{
register struct mhead *p;
register unsigned long tocopy;
register long nunits;
/* Check for maximum block size */
if (n > SEGMAX - sizeof(struct mhead)) {
WARNING("Can't allocate a block that large");
return(0);
}
/* if pointer is nil just malloc */
if ((p = (struct mhead *) mem) == 0)
return Lmalloc (n);
/* find the header information */
p--;
nunits = p -> mh_index;
tocopy = p -> mh_size;
/* If desired size fits in current block, just mark its size as changed. */
if (nunits == find_block_index(n))
{
p -> mh_size = (unsigned short) n;
return mem;
}
if (n < tocopy)
tocopy = n;
{
register char *new;
if ((new = Lmalloc (n)) == 0)
return 0;
memcpy (new, mem, (size_t) tocopy);
Lfree (mem);
return new;
}
}
syntax highlighted by Code2HTML, v. 0.9.1