Sindbad~EG File Manager
/* Dynamic memory allocation, M. Beeson */
/* Replacements for malloc, free, realloc, designed for
use with heapmax, and for the support of multiple heaps.
/*
11.25.92 original date
5.23.97 last modified before release of Mathpert 1.06
9.18.97 modified
1.12.98 changed ifndef _FLAT to ifdef __BORLANDC__
modified init_heap so SEGMENTSIZE is nonzero in 32-bit version.
changed 'unsigned' to 'unsigned short' in WORD and INDEX macros
2.28.98 changed heap_ok to check for increasing links and check for
correct links on lastnode. Added NEXTFREE(p) = NIL at line 459.
6.2.98 changed heap_ok to check link integrity in the free list too.
11.7.98 corrected reallocate in the event the block can be enlarged.
11.13.98 changed _ to .
5.30.01 corrected line -3 of init_heap, which is never executed in Mathpert anyway.
10.26.03 deleted '' and references to Borland C
9.1.04 deleted #include <windows.h>, and deleted redefinition of assert.
*/
#define HEAP_DLL
#include "export.h"
#include "heap.h"
#include "avail.h" /* externs for avail and mem */
/* Memory is thought of as an array of 'units'. Each unit
will occupy (1 << UNITSHIFT) bytes. The point is that this way
you can use integer indices into an array of more than 64K bytes.
This file assumes ints and unsigneds are two bytes.
*/
#include <stdio.h> /* for NULL */
#include <assert.h>
#include <string.h> /* for memcpy and memset */
static unsigned int maxunits; /* the number of units to be available */
unit *mem; /* pre_allocated array of units of dimension maxunits */
unsigned avail; /* mem[avail] is the next space to be allocated */
unsigned lastnode; /* mem[lastnode] is the last node, which is
always a free node */
#define NIL 0xffff /* used to mark the last link field in a list */
/* 32-bit version, the segment-size limitation is that SIZE(p) must fit in 15 bits */
#define SEGMENTSIZE 0x4000U
#define SEGMENT_BOUNDARY(p) (((unsigned short)(p) << 14U) == 0)
/* Limits on the number and size of units and memory blocks:
So-called "links" are 16-bit indices into the mem array; the
last one, NIL, is used to signal a null link. The size field is only 15
bits, so the largest size of a single allocated block is 0x7fff units,
that is, 32K units, which at 16 bytes per unit is 512K. Under
DOS and Windows, however, no block can exceed 64K anyway.
The total amount of memory that can be accessed is 64K times
sizeof(unit); if units are 16 bytes, then this is 1 megabyte. */
/* The first two (16-bit) words of a used block, and four words of a free
block, is overhead. In general (if sizeof(unit) changes) the overhead
(of a used block) will always be the same. The first two words contain a
pointer to the next block (free or not), an indicator whether the block is
used or free, and the size of the block INCLUDING the overhead. Thus we
maintain a singly-linked list of all nodes. We also maintain a doubly-linked
list of the free nodes, using the third and fourth words of the block
(that is, the first two words beyond the overhead area).
The global variable 'avail' points to the beginning of the free list;
that is, mem[avail] is the first free node. The LAST node is never
allocated, so there is always a node at the end of the mem array which
is in the free list, marked with NIL in the NEXTFREE position.
The 'size' field, 15 bits long, tells the number of usable units in the
block, INCLUDING the first unit which contains the overhead.
The third byte (free blocks only) contains a pointer to the
next free block. The following macros access and set this information. */
/* WORD gets the i-th 16-bit word of unit p */
#define WORD(i,p) (*((unsigned short*)(mem + (p)) + (i)))
#define LINK(p) WORD(1,p) /* next node */
#define PREVFREE(p) WORD(2,p) /* previous free node */
#define NEXTFREE(p) WORD(3,p) /* next free node */
#define SIZE(p) (WORD(0,p) & 0x7fffU) /* measured in units, not bytes */
#define SETSIZE(p,n) (WORD(0,p) = (n | (WORD(0,p) & 0x8000)))
#define USED(p) (WORD(0,p) >> 15) /* leftmost bit */
#define SETUSED(p) (WORD(0,p) |= 0x8000) /* set the leftmost bit to 1 */
#define SETUNUSED(p) (WORD(0,p) &= 0x7fff) /* set the leftmost bit to 0 */
#define INDEX(address) (unsigned short) (((unsigned long) ((char *) address\
- (char *) mem - 4)) >> UNITSHIFT)
/* INDEX(address) is the p such that address is four bytes beyond mem[p].
To make sure four-byte arithmetic is performed on
pointers, we cast them to *. Bit-shifting by UNITSHIFT to divide,
we get a two-byte value. INDEX can be applied to addresses returned
by mallocate. */
#define ADDRESS(p) (((char *) (mem + p)) + 4)
/* the address returned by mallocate for the block starting at mem[p] */
#define LASTNODE(p) (!USED(p) && LINK(p) == NIL && NEXTFREE(p) == NIL)
/* This macro was used for debugging: assert(LASTNODE(lastnode)) to
verify that lastnode is being properly maintained. */
/*__________________________________________________________________________*/
MEXPORT_HEAP void init_heap(unsigned n)
/* must be called before using functions defined in this file, and
AFTER allocating space for 'mem'. It initializes the heap to size n (units).
It creates as many nodes of size SEGMENTSIZE as required to fill up n units.
It sets maxunits to n and initializes avail to zero.
It stores a nonzero value of n in a static variable and uses that stored
value if n is passed as zero.
*/
{ static unsigned heapsize;
unsigned nblocks;
unsigned i,p;
if(n==0)
n = heapsize;
else
heapsize = n;
nblocks = heapsize/SEGMENTSIZE;
if(nblocks * SEGMENTSIZE < heapsize)
++nblocks;
for(i=0;i<nblocks;i++)
{ p = i*SEGMENTSIZE;
SETUNUSED(p);
if(i<nblocks-1)
{ NEXTFREE(p) = p+SEGMENTSIZE;
LINK(p) = p + SEGMENTSIZE;
SETSIZE(p,SEGMENTSIZE);
PREVFREE(p) = i ? p-SEGMENTSIZE : NIL;
}
else /* the last block */
{ /* SEGMENTSIZE divides heapsize, this is the last block */
NEXTFREE(p) = NIL;
LINK(p) = NIL;
lastnode = p;
PREVFREE(p) = i ? p-SEGMENTSIZE : NIL;
if(nblocks * SEGMENTSIZE == heapsize)
SETSIZE(p,SEGMENTSIZE);
else
SETSIZE(p,heapsize - (nblocks-1) * SEGMENTSIZE);
}
}
maxunits = n;
avail = 0;
}
/*__________________________________________________________________________*/
MEXPORT_HEAP void * callocate(unsigned long n, unsigned size)
/* replace the standard calloc() */
{ void *ans;
unsigned long nbytes = n*size;
ans = mallocate(nbytes);
if(ans == NULL)
return NULL;
if(nbytes <= 0xffff)
memset(ans,0,(unsigned) nbytes);
else
{ void *marker = ans;
while(nbytes > 0xffff)
{ memset(marker,0,0xffff);
nbytes -= 0xffff;
}
memset(marker,0,(unsigned) nbytes);
}
return ans;
}
/*__________________________________________________________________________*/
MEXPORT_HEAP void * mallocate(unsigned long nbytes)
/* replacement for C-language malloc. */
/* Requires nbytes > 0 */
/* nbytes >> UNITSHIFT must fit in two bytes */
{ unsigned p,q,k,r,s;
unsigned long nunits;
unsigned units;
nbytes += 4; /* add 4 bytes for the overhead */
nunits = nbytes >> UNITSHIFT;
units = (unsigned) nunits;
assert(nbytes); /* nbytes can't be zero */
if( (units << UNITSHIFT) < nbytes)
{ /* number of bytes wasn't an exact multiple of sizeof(unit) */
++units; /* this can't overflow because we have 0x7ffe, not 0x7fff
in the too-large test above */
}
for(p=avail; p!=NIL && SIZE(p) < units; p = NEXTFREE(p))
/* assert(!USED(p)) */ ;
if(p==NIL)
return NULL; /* can't get enough space */
#ifdef DEBUG_HEAP /* used for debugging */
if(p > lastnode)
assert(0);
if(p < avail)
assert(0);
#endif
/* OK, we found the space. Now allocate it. */
k = SIZE(p) - units;
q = PREVFREE(p);
if(q >= p && q != NIL)
assert(0); /* That is, assert(q < p || q == NIL); */
if(k==0) /* then just delete p from the free list */
{ r = NEXTFREE(p);
if(r == NIL) /* p is the last free node on the heap */
return NULL; /* not allowed to use up every last byte, as we
always need a 'next free node' */
if(r <= p)
assert(0);
if(p==avail)
{ avail = r;
/* assert(q==NIL); This was the first node on the free list */
PREVFREE(avail) = NIL;
}
else /* This wasn't the first node, leave avail unchanged */
{ assert(q !=NIL);
assert(q < r);
NEXTFREE(q) = r;
PREVFREE(r) = q;
/* assert(avail <= q); */
}
SETUSED(p);
return ADDRESS(p); /* four bytes beyond &mem[p] */
}
/* and if the space available at p is not all required, then
make a new node r and insert it after p */
r = p+units;
SETSIZE(r,k);
SETSIZE(p,units);
SETUSED(p);
SETUNUSED(r);
s = NEXTFREE(p);
NEXTFREE(r) = s;
PREVFREE(r) = q;
if(q != NIL)
NEXTFREE(q) = r;
else
avail = r;
if(s != NIL)
PREVFREE(s) = r;
LINK(r) = LINK(p);
LINK(p) = r;
if(p==lastnode)
{ lastnode = r; /* maintain the correct value of lastnode */
}
return ADDRESS(p);
}
/*__________________________________________________________________________*/
MEXPORT_HEAP void free2(void *address)
/* free a node, maintaining the integrity of the heap by properly updating
both the list of all nodes and the doubly-linked list of free nodes.
Merge the freed node with either or both of its neighbors if they are
also free. Since we can't directly find the previous node, we must
find it as follows: go forwards to the next free node, then back to the
previous free node and see if that is the node before p. */
{ unsigned next,prevfree,nextfree,p;
#ifdef DEBUG_HEAP
unsigned old;
#endif
p = INDEX(address);
if(p > lastnode || !USED(p))
assert(0); /* it's an error to free a node not in use;
also if p > lastnode then free2 is being
erroneously called, even if the node still
SAYS it is in use. */
/* Putting it this way instead of assert(!USED(p))
enables me to put a breakpoint on the assert(0) line
and stop exactly when the assertion fails, so I
can inspect the stack and find who has mis-called free2 */
next = LINK(p);
/* find the next free node after p */
for(nextfree=next; USED(nextfree); nextfree=LINK(nextfree))
#ifdef DEBUG_HEAP
{ if(nextfree > next && nextfree <= old)
assert(0);
old = nextfree;
assert(nextfree != NIL); /* last node is never allocated */
}
#else
;
#endif
prevfree = PREVFREE(nextfree);
if(prevfree != NIL && prevfree >= p)
assert(0);
/* Now, do we have to merge? */
if(prevfree != NIL && LINK(prevfree) == p && nextfree != next && ! SEGMENT_BOUNDARY(p))
{ /* merge with previous node only */
LINK(prevfree) = next;
SETSIZE(prevfree,SIZE(prevfree) + SIZE(p));
assert(avail <= prevfree);
/* If there was a previous free node, we don't need to
change avail */
}
else if(nextfree == next && (prevfree == NIL || LINK(prevfree) != p) && !SEGMENT_BOUNDARY(next))
{ /* merge with next node only */
SETSIZE(p,SIZE(p) + SIZE(next));
LINK(p) = LINK(next);
SETUNUSED(p);
nextfree = NEXTFREE(next);
NEXTFREE(p) = nextfree;
PREVFREE(p) = prevfree;
assert(prevfree < p || prevfree == NIL);
if(nextfree != NIL)
{ assert(p < nextfree);
PREVFREE(nextfree) = p;
}
if(prevfree != NIL)
NEXTFREE(prevfree) = p;
if(p < avail)
avail = p;
if(next == lastnode)
{ lastnode = p;
}
}
else if(nextfree == next && prevfree != NIL && LINK(prevfree)==p &&
! SEGMENT_BOUNDARY(next) && ! SEGMENT_BOUNDARY(p)
)
{ /* merge in both directions */
LINK(prevfree) = LINK(next);
SETSIZE(prevfree,SIZE(prevfree) + SIZE(p) + SIZE(next));
NEXTFREE(prevfree) = NEXTFREE(next);
assert(prevfree < NEXTFREE(prevfree));
/* PREVFREE(prevfree) is ok */
if(NEXTFREE(next) != NIL)
{ assert(prevfree < NEXTFREE(next));
PREVFREE(NEXTFREE(next)) = prevfree;
}
/* No need to adjust avail because: */
assert(avail <= prevfree);
if(next == lastnode)
{ lastnode = prevfree;
}
}
else /* no merge at all, just insert p in the free list */
{ NEXTFREE(p) = nextfree;
PREVFREE(p) = prevfree;
assert(prevfree < p || prevfree == NIL);
PREVFREE(nextfree) = p;
assert(p < nextfree);
SETUNUSED(p);
if(prevfree == NIL)
avail = p;
else
{ NEXTFREE(prevfree) = p;
assert(avail <= prevfree);
}
}
}
/*__________________________________________________________________*/
MEXPORT_HEAP void * heapmax(void)
/* Return the address of the largest allocated node. */
/* Return NULL if there is no USED node. */
/* For efficiency, don't just traverse the whole list of
all nodes. Instead, start with lastnode, and back
up until you find a skip in the free list. */
{ unsigned p,q;
unsigned ans=NIL;
q = lastnode; /* last node, and also last free node */
if(q==0) /* there are no allocated nodes */
return NULL;
p = PREVFREE(q);
if(p == NIL)
{ /* q was also the FIRST free node */
/* just traverse the heap from the beginning */
for(p=0; p!=q; p = LINK(p))
ans = p;
return ADDRESS(ans); /* The node before q */
}
while(p != NIL && LINK(p) == q)
{ q = p;
p = PREVFREE(q);
}
if(p==NIL && q == 0)
return NULL; /* there was no used node, but the heapsize
was more than 64K bytes so q was not initially zero */
if(p==NIL && LINK(0) == q)
{ if(!USED(0))
return NULL; /* There was no used node at all */
return ADDRESS(0); /* Only one node was in use */
}
if(p==NIL)
p = 0;
/* Now LINK(p) != q but p = PREVFREE(q), so there
must be at least one allocated node in between */
while(LINK(p) < q)
p = LINK(p); /* all these nodes are in use */
if(LINK(p)!=q)
assert(0);
return ADDRESS(p);
}
/*__________________________________________________________________*/
MEXPORT_HEAP void reset_heap(void *addr)
/* free every used node on the heap whose address is greater than 'addr'.
Do it directly, just creating the desired lists, rather than walking
the heap past that point, so this function will execute at lightning speed.
Addr must at one time have been on the heap, but it need not be so
any longer. */
{ unsigned p;
unsigned q,trailer,prevfree,marker;
if(addr == NULL)
{ init_heap(0); /* restore initial condition of heap */
return;
}
p = INDEX(addr);
for(q=0;q<=p && q!=NIL;q=LINK(q))
trailer = q; /* advance until q > p */
if(q==NIL)
return; /* No node exists past addr to free */
/* Now trailer is the last node NOT to be freed and q is the
first node to be freed (if it is a used node). We need to
find the last free node <= p. To do that we have to keep
walking the heap till we find a free node, then back up
with PREVFREE. */
marker = q;
while(USED(marker) && marker != NIL)
marker = LINK(marker);
if(marker == NIL)
return; /* No node exists past addr to free */
prevfree = PREVFREE(marker); /* the last free node <= p */
if(prevfree > p && prevfree != NIL)
assert(0);
/* make a new last node containing all memory past and including q */
/* or if necessary to avoid crossing segment boundaries, make
several new nodes. */
LINK(trailer) = q;
if(prevfree != NIL)
NEXTFREE(prevfree)=q;
PREVFREE(q) = prevfree;
assert(prevfree < q || prevfree == NIL);
SETUNUSED(q);
if(q < avail)
avail = q;
/* Be careful not to create a node crossing segment boundaries. */
if((maxunits-1)/SEGMENTSIZE != q/SEGMENTSIZE)
{ /* then we have to create more than one new node */
/* Make one that takes us up to the next segment boundary */
p = (q/SEGMENTSIZE)*SEGMENTSIZE + SEGMENTSIZE;
SETSIZE(q, p-q);
while(p+SEGMENTSIZE <= maxunits)
{ SETUNUSED(p);
PREVFREE(p) = q;
SETSIZE(p,SEGMENTSIZE);
NEXTFREE(q) = LINK(q) = p;
q = p;
p += SEGMENTSIZE;
}
if(p < maxunits)
{ /* make one last node */
NEXTFREE(q) = LINK(q) = p;
SETUNUSED(p);
SETSIZE(p,maxunits-p);
PREVFREE(p) = q;
LINK(p) = NIL;
NEXTFREE(p) = NIL;
lastnode = p;
}
else /* q was the last node */
{ NEXTFREE(q) = LINK(q) = NIL;
lastnode = q;
}
}
else
{ /* just make one new node */
NEXTFREE(q) = LINK(q) = NIL; /* q is the last node in both lists */
SETSIZE(q, maxunits - q);
lastnode = q;
}
}
/*__________________________________________________________________*/
MEXPORT_HEAP void * reallocate(void *addr, unsigned newsize)
/* Unlike the usual realloc, this one also sets the new part
of the memory to zero. Note: if the new size requested isn't an exact
multiple of sizeof(unit), the last fraction of a unit won't be set to
zero; only up to the number of bytes requested is set to zero. */
/* newsize must be larger than oldsize */
/* oldsize and newsize are in bytes, not units */
/* This realloc can't be used to decrease
the size of a block, only to increase it. */
{ void *ans;
unsigned p,q,r,nextfree,prevfree,size,desiredsize,oldsize;
if(addr == NULL)
return mallocate(newsize);
p = INDEX(addr);
size = SIZE(p);
if(newsize > 0xfffb)
return NULL; /* can't realloc */
desiredsize = (unsigned) ((newsize + 4) << UNITSHIFT);
/* required new size in units */
if(desiredsize <= size)
return addr; /* we don't downsize */
oldsize = size << UNITSHIFT;
if(desiredsize & (0xffff > (16-UNITSHIFT)))
++desiredsize; /* adjust if a fraction of a unit more is required */
q = p;
while(size < desiredsize && !USED(q) && q != NIL &&
q/SEGMENTSIZE == p/SEGMENTSIZE /* can't cross segment boundaries */
)
{ q = LINK(q);
size += SIZE(q);
}
if(USED(q) || q == NIL)
/* you can't extend the block */
{ ans = mallocate(newsize);
if(ans == NULL)
return NULL;
memcpy(ans,addr,oldsize);
memset((char *) ans + oldsize, 0, newsize - oldsize);
free2(addr);
return ans;
}
/* There IS enough space to extend the block */
prevfree = PREVFREE(LINK(p));
if(size <= desiredsize+1) /* the last block is entirely used up */
{ LINK(p) = LINK(q);
SETSIZE(p,size);
nextfree = NEXTFREE(q); /* q was a free block so NEXTFREE makes sense */
if(nextfree == NIL)
return NULL; /* q was the last block */
r = LINK(q);
NEXTFREE(prevfree) = r; /* adjust the list of free blocks */
PREVFREE(r) = prevfree;
NEXTFREE(r) = nextfree;
PREVFREE(nextfree) = r;
}
else /* the last block isn't entirely used up */
{ SETSIZE(p,desiredsize);
LINK(p) = q + SIZE(q) - (size-desiredsize);
r = LINK(p); /* create a new block at r */
nextfree = NEXTFREE(q); /* q was free */
LINK(r) = LINK(q);
SETUNUSED(r);
SETSIZE(r,size-desiredsize); /* second arg is positive */
NEXTFREE(r) = nextfree; /* set the links from r */
PREVFREE(r) = prevfree;
PREVFREE(nextfree) = r; /* adjust the backlink to r */
NEXTFREE(prevfree) = r; /* adjust the forward link to r */
if(q == lastnode)
lastnode = r;
}
ans = addr;
memset((char *) ans + oldsize, 0, newsize - oldsize);
return ans;
}
/*____________________________________________________________*/
MEXPORT_HEAP unsigned long mycoreleft(void)
/* how much heap space is available? in bytes */
{ unsigned long ans = 0;
unsigned marker = avail;
while(marker != NIL)
{ ans += SIZE(marker);
marker = NEXTFREE(marker);
}
return ans * sizeof(unit);
}
/*____________________________________________________________*/
MEXPORT_HEAP int heap_ok(void)
/* For debugging only. Verify that walking the heap from
the beginning will hit the node 'avail' and then eventually, lastnode.
Also verify link integrity in the free list.
Return 1 if the heap is OK, 0 if not.
*/
{ unsigned p,q=0;
int flag = 0;
unsigned prevfree,marker,oldmarker;
for(p=0;p != NIL; p = LINK(p))
{ if(p == avail)
flag = 1;
if(p > avail && !flag)
return 0;
if(p < q)
return 0; /* wrong link */
if(p == lastnode)
{ if(LINK(p) != NIL)
return 0;
if(NEXTFREE(p) != NIL)
return 0;
break;
}
if(!USED(p))
{ if(PREVFREE(p) == NIL && p > avail)
return 0;
if(p < avail)
return 0;
}
q = p;
}
if(p == NIL)
return 0;
marker = 0;
while(USED(marker) && marker != NIL)
{ oldmarker = marker;
marker = LINK(marker);
if(marker <= oldmarker)
return 0; /* links are supposed to increase */
if(marker > lastnode)
return 0; /* this and the previous clause together
guarantee that heap_ok will ALWAYS terminate
no matter how corrupt the heap may be. */
}
if(marker == NIL)
return 0; /* entire heap is full, but there's always
supposed to be at least one free node */
if(marker != avail)
return 0;
prevfree = PREVFREE(marker);
if(prevfree != NIL)
return 0;
/* Now verify link integrity in the free list starting backwards from lastnode */
marker = lastnode;
while(marker != NIL)
{ oldmarker = marker;
marker = PREVFREE(marker);
if(marker == NIL)
{ if(oldmarker != avail)
return 0;
break;
}
if(NEXTFREE(marker) != oldmarker)
return 0;
if(marker < avail && marker != NIL)
return 0;
if(oldmarker == avail && marker != NIL)
return 0;
}
return 1;
}
Sindbad File Manager Version 1.0, Coded By Sindbad EG ~ The Terrorists