672 lines
17 KiB
C
672 lines
17 KiB
C
/* $NetBSD: malloc.c,v 1.2 1997/04/13 10:50:30 mrg Exp $ */
|
||
|
||
/*
|
||
* malloc/free/realloc memory management routines.
|
||
*
|
||
* This is a very simple, but fast storage allocator. It allocates blocks
|
||
* of a small number of different sizes, and keeps free lists of each size.
|
||
* Blocks that don't exactly fit are passed up to the next larger size.
|
||
* In this implementation, the available sizes are (2^(i+4))-8 bytes long.
|
||
* This is designed for use in a single-threaded virtual memory environment.
|
||
*
|
||
* This version is derived from: malloc.c (Caltech) 2/21/82, Chris Kingsley,
|
||
* kingsley@cit-20, and a similar version of Larry Wall, used by perl.
|
||
*
|
||
* Rewritten by Eric Wassenaar, Nikhef-H, <e07@nikhef.nl>
|
||
*
|
||
* Not only varies the vendor-supplied implementation of this package greatly
|
||
* for different platforms, there are also many subtle semantic differences,
|
||
* especially for anomalous conditions. This makes it risky to use a separate
|
||
* package, in case other library routines depend on those special features.
|
||
* Nevertheless, we assume that external routines under normal circumstances
|
||
* just need the basic malloc/free/realloc functionality.
|
||
*
|
||
* To avoid possible conflicts, included in this package are some auxiliary
|
||
* functions: memalign, calloc/cfree, valloc/vfree, because they are closely
|
||
* related to and built upon the basic functions.
|
||
*
|
||
* Not supported are special functions: mallopt/mallinfo/mstats, and those
|
||
* which are found on specific platforms only: mallocblksize/recalloc (sgi),
|
||
* malloc_debug/malloc_verify (sun), malloc_size/malloc_error (next).
|
||
*
|
||
* The simplicity of this package imposes the following limitations:
|
||
* - Memory once allocated is never returned to the system, but is put on
|
||
* free lists for subsequent use. The process size grows monotonously.
|
||
* - Allocating zero-size data blocks is not an error.
|
||
* - In case realloc fails, the old block may no longer be allocated.
|
||
* - The strictest alignment for memalign is the page size.
|
||
*/
|
||
|
||
#ifndef lint
|
||
static char Version[] = "@(#)malloc.c e07@nikhef.nl (Eric Wassenaar) 961019";
|
||
#endif
|
||
|
||
#if defined(apollo) && defined(lint)
|
||
#define __attribute(x)
|
||
#endif
|
||
|
||
#include <stdio.h>
|
||
#include <errno.h>
|
||
#include <sys/types.h>
|
||
#include <sys/param.h>
|
||
|
||
#ifdef lint
|
||
#define EXTERN
|
||
#else
|
||
#define EXTERN extern
|
||
#endif
|
||
|
||
EXTERN int errno;
|
||
|
||
/*
|
||
* Portability definitions. These are probably too primitive, but the
|
||
* semantics on various platforms are too chaotic to do it correctly.
|
||
*/
|
||
|
||
#if defined(SYSV) || defined(SVR4)
|
||
#define SYSV_MALLOC
|
||
#define SYSV_MEMSET
|
||
#endif
|
||
|
||
#ifdef SYSV_MALLOC
|
||
typedef void ptr_t;
|
||
typedef u_int siz_t;
|
||
typedef void free_t;
|
||
#define free_return(x) return
|
||
#else
|
||
typedef char ptr_t;
|
||
typedef u_int siz_t;
|
||
typedef int free_t;
|
||
#define free_return(x) return(x)
|
||
#endif
|
||
|
||
#ifdef SYSV_MEMSET
|
||
#define bzero(a,n) (void) memset(a,'\0',n)
|
||
#define bcopy(a,b,n) (void) memcpy(b,a,n)
|
||
#endif
|
||
|
||
/*
|
||
* The page size used to request blocks of system memory.
|
||
*/
|
||
|
||
#ifndef PAGESIZE
|
||
#ifdef NBPG
|
||
|
||
#ifdef CLSIZE
|
||
#define PAGESIZE (NBPG*CLSIZE)
|
||
#else
|
||
#define PAGESIZE NBPG
|
||
#endif
|
||
|
||
#else /*not NBPG*/
|
||
|
||
#ifdef NBPC
|
||
#define PAGESIZE NBPC
|
||
#else
|
||
#define PAGESIZE 4096 /* some reasonable default */
|
||
#endif
|
||
|
||
#endif /*NBPG*/
|
||
#endif
|
||
|
||
static int pagesize = 0; /* page size after initialization */
|
||
|
||
/*
|
||
* The overhead on a block is 8 bytes on traditional 32-bit platforms.
|
||
* When free, this space contains a pointer to the next free block,
|
||
* and the lower 3 bits of this pointer must be zero.
|
||
* When in use, the first field is set to OVMAGIC with the lower 3 bits
|
||
* nonzero, and the second field is the hash bucket index.
|
||
* The overhead information precedes the data area returned to the user.
|
||
* When special alignment is required, a fragment is created within the
|
||
* data block, preceded by a special fragment header containing a magic
|
||
* number FRMAGIC and the align offset from the overlapping block.
|
||
*/
|
||
|
||
typedef union overhead {
|
||
|
||
struct {
|
||
union overhead *ovfree_next; /* next chunk on free list */
|
||
} ov_free;
|
||
|
||
struct {
|
||
int ovused_magic; /* magic number */
|
||
int ovused_index; /* bucket index */
|
||
} ov_used;
|
||
|
||
struct {
|
||
int ovfrag_fmagic; /* fragment magic number */
|
||
int ovfrag_offset; /* fragment align offset */
|
||
} ov_frag;
|
||
|
||
/* align on double word boundary */
|
||
double ov_align;
|
||
|
||
} OVHDR;
|
||
|
||
#define ov_next ov_free.ovfree_next /* next on free list */
|
||
#define ov_magic ov_used.ovused_magic /* magic number */
|
||
#define ov_index ov_used.ovused_index /* bucket index */
|
||
#define ov_fmagic ov_frag.ovfrag_fmagic /* fragment magic number */
|
||
#define ov_offset ov_frag.ovfrag_offset /* fragment align offset */
|
||
|
||
#define OVMAGIC 0xef /* overhead magic number */
|
||
#define FRMAGIC 0x5555 /* fragment magic number */
|
||
#define OVHDRSZ sizeof(OVHDR) /* size of overhead data */
|
||
|
||
/*
|
||
* freelist[i] is the pointer to the next free chunk of size 2^(i+4).
|
||
* The smallest allocatable chunk is 16 bytes, containing a user data
|
||
* block of 8 bytes (assuming 8 bytes overhead on 32-bit platforms).
|
||
*/
|
||
|
||
#define MINCHUNK 4 /* minimum chunk size 2^(0+4) */
|
||
#define MAXCHUNK 30 /* maximum chunk size 2^(26+4) */
|
||
#define NBUCKETS 27 /* MAXCHUNK - MINCHUNK + 1 */
|
||
|
||
static OVHDR *freelist[NBUCKETS]; /* hash list of free chunks */
|
||
static int malloced[NBUCKETS]; /* number of allocated chunks */
|
||
|
||
#define chunk_size(i) (1 << ((i) + MINCHUNK))
|
||
#define data_size(i) (chunk_size(i) - OVHDRSZ)
|
||
#define MINDATA ((1 << MINCHUNK) - OVHDRSZ)
|
||
#define MAXDATA ((1 << MAXCHUNK) - OVHDRSZ)
|
||
|
||
#define get_offset(a,b) ((unsigned long)(a) & ((b) - 1))
|
||
#define word_offset(a) get_offset(a, OVHDRSZ)
|
||
#define page_offset(a) get_offset(a, PAGESIZE)
|
||
#define aligned(a,b) (get_offset(a,b) == 0)
|
||
#define word_aligned(a) aligned(a, OVHDRSZ)
|
||
#define page_aligned(a) aligned(a, PAGESIZE)
|
||
#define auto_aligned(a) aligned(a, a)
|
||
|
||
#define valid_index(i) ((i) >= 0 && (i) < NBUCKETS)
|
||
#define valid_offset(i) ((i) > OVHDRSZ && auto_aligned(i) && (i) <= PAGESIZE)
|
||
|
||
/*
|
||
* Definition of modules.
|
||
*/
|
||
|
||
#define PROTO(TYPES) ()
|
||
|
||
ptr_t *malloc PROTO((siz_t));
|
||
ptr_t *memalign PROTO((siz_t, siz_t));
|
||
free_t free PROTO((ptr_t *));
|
||
ptr_t *realloc PROTO((ptr_t *, siz_t));
|
||
ptr_t *calloc PROTO((siz_t, siz_t));
|
||
free_t cfree PROTO((ptr_t *));
|
||
ptr_t *valloc PROTO((siz_t));
|
||
free_t vfree PROTO((ptr_t *));
|
||
|
||
extern ptr_t *sbrk PROTO((int));
|
||
|
||
/*
|
||
** MALLOC -- Allocate more memory
|
||
** ------------------------------
|
||
*/
|
||
|
||
ptr_t *
|
||
malloc(size)
|
||
siz_t size; /* amount of memory to allocate */
|
||
{
|
||
register OVHDR *op; /* chunk pointer */
|
||
register int bucket; /* hash bucket index */
|
||
register int bucketsize; /* size of hash bucket chunk */
|
||
register int memsize; /* amount of memory to expand */
|
||
|
||
/*
|
||
* First time malloc is called, do some sanity checks, setup page size,
|
||
* and align the break pointer so all chunk data will be page aligned.
|
||
* Note. Cannot issue debugging print statements during initialization.
|
||
*/
|
||
if (pagesize == 0)
|
||
{
|
||
if (page_offset(PAGESIZE) || word_offset(OVHDRSZ))
|
||
{
|
||
errno = EINVAL;
|
||
return(NULL);
|
||
}
|
||
|
||
op = (OVHDR *)sbrk(0);
|
||
if (op == NULL || (char *)op == (char *)-1)
|
||
{
|
||
errno = ENOMEM;
|
||
return(NULL);
|
||
}
|
||
|
||
memsize = page_offset(op);
|
||
if (memsize > 0)
|
||
{
|
||
memsize = PAGESIZE - memsize;
|
||
op = (OVHDR *)sbrk(memsize);
|
||
if (op == NULL || (char *)op == (char *)-1)
|
||
{
|
||
errno = ENOMEM;
|
||
return(NULL);
|
||
}
|
||
}
|
||
|
||
/* initialization complete */
|
||
pagesize = PAGESIZE;
|
||
}
|
||
|
||
/*
|
||
* Convert amount of memory requested into closest chunk size
|
||
* stored in hash buckets which satisfies request.
|
||
*/
|
||
bucket = 0; bucketsize = chunk_size(bucket);
|
||
while (bucketsize < OVHDRSZ || size > (bucketsize - OVHDRSZ))
|
||
{
|
||
bucket++; bucketsize <<= 1;
|
||
if (bucket >= NBUCKETS)
|
||
{
|
||
errno = EINVAL;
|
||
return(NULL);
|
||
}
|
||
}
|
||
|
||
/*
|
||
* If nothing in hash bucket right now, request more memory from the system.
|
||
* Add new memory allocated to that on free list for this hash bucket.
|
||
* System memory is expanded by increments of whole pages. For small chunk
|
||
* sizes, the page is subdivided into a list of free chunks.
|
||
*/
|
||
if (freelist[bucket] == NULL)
|
||
{
|
||
memsize = (bucketsize < PAGESIZE) ? PAGESIZE : bucketsize;
|
||
op = (OVHDR *)sbrk(memsize);
|
||
if (op == NULL || (char *)op == (char *)-1)
|
||
{
|
||
errno = ENOMEM;
|
||
return(NULL);
|
||
}
|
||
|
||
freelist[bucket] = op;
|
||
while (memsize > bucketsize)
|
||
{
|
||
memsize -= bucketsize;
|
||
op->ov_next = (OVHDR *)((char *)op + bucketsize);
|
||
op = op->ov_next;
|
||
}
|
||
op->ov_next = NULL;
|
||
}
|
||
|
||
/*
|
||
* Memory is available.
|
||
*/
|
||
/* remove from linked list */
|
||
op = freelist[bucket];
|
||
freelist[bucket] = op->ov_next;
|
||
malloced[bucket]++;
|
||
|
||
/* mark this chunk in use */
|
||
op->ov_magic = OVMAGIC;
|
||
op->ov_index = bucket;
|
||
|
||
/* return pointer to user data block */
|
||
return((ptr_t *)((char *)op + OVHDRSZ));
|
||
}
|
||
|
||
/*
|
||
** MEMALIGN -- Allocate memory with alignment constraints
|
||
** ------------------------------------------------------
|
||
*/
|
||
|
||
ptr_t *
|
||
memalign(align, size)
|
||
siz_t align; /* required memory alignment */
|
||
siz_t size; /* amount of memory to allocate */
|
||
{
|
||
register OVHDR *op; /* chunk pointer */
|
||
register ptr_t *newbuf; /* new block of user data */
|
||
register int offset = 0; /* fragment offset for alignment */
|
||
|
||
/*
|
||
* The alignment must be a power of two, and no bigger than the page size.
|
||
*/
|
||
if ((align == 0) || !auto_aligned(align) || (align > PAGESIZE))
|
||
{
|
||
errno = EINVAL;
|
||
return(NULL);
|
||
}
|
||
|
||
/*
|
||
* For ordinary small alignment sizes, we can use the plain malloc.
|
||
*/
|
||
if (align > OVHDRSZ)
|
||
offset = align - OVHDRSZ;
|
||
|
||
newbuf = malloc(size + offset);
|
||
if (newbuf == NULL)
|
||
return(NULL);
|
||
|
||
/*
|
||
* Otherwise we have to create a more strictly aligned fragment.
|
||
*/
|
||
if (offset > 0)
|
||
{
|
||
/* locate the proper alignment boundary within the block */
|
||
newbuf = (ptr_t *)((char *)newbuf + offset);
|
||
|
||
/* mark this block as a special fragment */
|
||
op = (OVHDR *)((char *)newbuf - OVHDRSZ);
|
||
op->ov_fmagic = FRMAGIC;
|
||
op->ov_offset = OVHDRSZ + offset;
|
||
}
|
||
|
||
return(newbuf);
|
||
}
|
||
|
||
/*
|
||
** DEALLOC -- Put unneeded memory on the free list
|
||
** -----------------------------------------------
|
||
*/
|
||
|
||
static int
|
||
dealloc(oldbuf)
|
||
ptr_t *oldbuf; /* old block of user data */
|
||
{
|
||
register OVHDR *op; /* chunk pointer */
|
||
register int bucket; /* hash bucket index */
|
||
register int offset; /* fragment offset for alignment */
|
||
|
||
/* if no old block, or if not yet initialized */
|
||
if (oldbuf == NULL || pagesize == 0)
|
||
{
|
||
errno = EINVAL;
|
||
return(0);
|
||
}
|
||
|
||
/* avoid bogus block addresses */
|
||
if (!word_aligned(oldbuf))
|
||
{
|
||
errno = EINVAL;
|
||
return(0);
|
||
}
|
||
|
||
/* move to the header for this chunk */
|
||
op = (OVHDR *)((char *)oldbuf - OVHDRSZ);
|
||
|
||
/* adjust in case this is an aligned fragment */
|
||
if ((op->ov_fmagic == FRMAGIC) && valid_offset(op->ov_offset))
|
||
{
|
||
offset = op->ov_offset - OVHDRSZ;
|
||
op = (OVHDR *)((char *)op - offset);
|
||
}
|
||
|
||
/* check whether this chunk was really allocated */
|
||
if ((op->ov_magic != OVMAGIC) || !valid_index(op->ov_index))
|
||
{
|
||
errno = EINVAL;
|
||
return(0);
|
||
}
|
||
|
||
/* put back on the free list for this bucket */
|
||
bucket = op->ov_index;
|
||
op->ov_next = freelist[bucket];
|
||
freelist[bucket] = op;
|
||
malloced[bucket]--;
|
||
|
||
return(1);
|
||
}
|
||
|
||
/*
|
||
** FREE -- Put unneeded memory on the free list
|
||
** --------------------------------------------
|
||
**
|
||
** In this implementation, unneeded memory is never returned to
|
||
** the system, but is put on a free memory hash list instead.
|
||
** Subsequent malloc requests will first examine the free lists
|
||
** to see whether a request can be satisfied.
|
||
** As a consequence of this strategy, the process size will grow
|
||
** monotonously, up to the largest amount needed at any moment.
|
||
**
|
||
** On some platforms, this routine does not return a status code.
|
||
** The status will be stored in a global variable ``free_status''
|
||
** which can be examined if desired.
|
||
*/
|
||
|
||
int free_status = 0; /* return code of last free */
|
||
|
||
free_t
|
||
free(oldbuf)
|
||
ptr_t *oldbuf; /* old block of user data */
|
||
{
|
||
free_status = dealloc(oldbuf);
|
||
free_return(free_status);
|
||
}
|
||
|
||
/*
|
||
** FINDBUCKET -- Locate a chunk of memory on the free list
|
||
** -------------------------------------------------------
|
||
**
|
||
** When a program attempts "storage compaction" as mentioned in the
|
||
** old malloc man page, it realloc's an already freed block. Usually
|
||
** this is the last block it freed; occasionally it might be farther
|
||
** back. We have to search all the free lists for the block in order
|
||
** to determine its bucket: first we make one pass thru the lists
|
||
** checking only the first block in each; if that fails we search
|
||
** the entire lists for a match. If still not found it is an error.
|
||
*/
|
||
|
||
static int
|
||
findbucket(oldop)
|
||
OVHDR *oldop; /* old chunk to search for */
|
||
{
|
||
register OVHDR *op; /* chunk pointer */
|
||
register int bucket; /* hash bucket index */
|
||
|
||
for (bucket = 0; bucket < NBUCKETS; bucket++)
|
||
{
|
||
if (freelist[bucket] == oldop)
|
||
return(bucket);
|
||
}
|
||
|
||
for (bucket = 0; bucket < NBUCKETS; bucket++)
|
||
{
|
||
for (op = freelist[bucket]; op != NULL; op = op->ov_next)
|
||
{
|
||
if (op == oldop)
|
||
return(bucket);
|
||
}
|
||
}
|
||
|
||
/* not found */
|
||
return(-1);
|
||
}
|
||
|
||
/*
|
||
** REALLOC -- Rellocate already allocated memory
|
||
** ---------------------------------------------
|
||
*/
|
||
|
||
ptr_t *
|
||
realloc(oldbuf, size)
|
||
ptr_t *oldbuf; /* old block of user data */
|
||
siz_t size; /* amount of memory to allocate */
|
||
{
|
||
register OVHDR *op; /* chunk pointer */
|
||
register ptr_t *newbuf; /* new block of user data */
|
||
register int bucket; /* hash bucket index */
|
||
register int offset = 0; /* fragment offset for alignment */
|
||
siz_t minsize, maxsize; /* size limits for this bucket */
|
||
int allocated = 0; /* set if old chunk was allocated */
|
||
|
||
/*
|
||
* Do plain malloc if no old block, or if not yet initialized.
|
||
* Otherwise, get the header, and check for special conditions.
|
||
*/
|
||
if (oldbuf == NULL || pagesize == 0)
|
||
{
|
||
newbuf = malloc(size);
|
||
return(newbuf);
|
||
}
|
||
|
||
/* avoid bogus block addresses */
|
||
if (!word_aligned(oldbuf))
|
||
{
|
||
errno = EINVAL;
|
||
return(NULL);
|
||
}
|
||
|
||
/* move to the header for this chunk */
|
||
op = (OVHDR *)((char *)oldbuf - OVHDRSZ);
|
||
|
||
/* adjust in case this is an aligned fragment */
|
||
if ((op->ov_fmagic == FRMAGIC) && valid_offset(op->ov_offset))
|
||
{
|
||
offset = op->ov_offset - OVHDRSZ;
|
||
op = (OVHDR *)((char *)op - offset);
|
||
}
|
||
|
||
/*
|
||
* Check whether this chunk is allocated at this moment.
|
||
* If not, try to locate it on the free list hash buckets.
|
||
*/
|
||
if ((op->ov_magic == OVMAGIC) && valid_index(op->ov_index))
|
||
{
|
||
allocated = 1;
|
||
bucket = op->ov_index;
|
||
}
|
||
else
|
||
{
|
||
bucket = findbucket(op);
|
||
if (bucket < 0)
|
||
{
|
||
errno = EINVAL;
|
||
return(NULL);
|
||
}
|
||
}
|
||
|
||
/*
|
||
* If the new size block fits into the same already allocated chunk,
|
||
* we can just use it again, avoiding a malloc and a bcopy. Otherwise,
|
||
* we can safely put it on the free list (the contents are preserved).
|
||
*/
|
||
/* make sure the alignment offset is consistent with the bucket */
|
||
if ((offset > 0) && (bucket == 0 || offset > data_size(bucket-1)))
|
||
{
|
||
errno = EINVAL;
|
||
return(NULL);
|
||
}
|
||
|
||
/* maximum data block size of this chunk */
|
||
maxsize = data_size(bucket) - offset;
|
||
|
||
if (allocated)
|
||
{
|
||
/* maximum data block size in the preceding hash bucket */
|
||
minsize = (bucket > 0) ? data_size(bucket-1) - offset : 0;
|
||
|
||
/* re-use same block if within bounds */
|
||
if (size <= maxsize && size > minsize)
|
||
return(oldbuf);
|
||
|
||
/* no longer useable */
|
||
(void) dealloc(oldbuf);
|
||
}
|
||
|
||
/*
|
||
* A new chunk must be allocated, possibly with alignment restrictions.
|
||
*/
|
||
newbuf = malloc(size + offset);
|
||
if (newbuf == NULL)
|
||
return(NULL);
|
||
|
||
if (offset > 0)
|
||
{
|
||
/* locate the proper alignment boundary within the block */
|
||
newbuf = (ptr_t *)((char *)newbuf + offset);
|
||
|
||
/* mark this block as a special fragment */
|
||
op = (OVHDR *)((char *)newbuf - OVHDRSZ);
|
||
op->ov_fmagic = FRMAGIC;
|
||
op->ov_offset = OVHDRSZ + offset;
|
||
}
|
||
|
||
/*
|
||
* Copy the contents of the old user data block into the new block.
|
||
* In case we shrink, only copy the requested amount of user data.
|
||
* If we expand, copy the maximum possible amount from the old block.
|
||
* Note that the exact amount of valid old user data is not known.
|
||
*/
|
||
if (oldbuf != newbuf)
|
||
bcopy(oldbuf, newbuf, (size < maxsize) ? size : maxsize);
|
||
|
||
return(newbuf);
|
||
}
|
||
|
||
/*
|
||
** CALLOC -- Allocate memory for number of elements, and clear it
|
||
** --------------------------------------------------------------
|
||
**
|
||
** This is a wrapper for malloc to request memory for a number
|
||
** of consecutive elements of certain length.
|
||
** As a side effect, the entire memory block obtained is cleared.
|
||
*/
|
||
|
||
ptr_t *
|
||
calloc(count, length)
|
||
siz_t count; /* number of elements */
|
||
siz_t length; /* size per element */
|
||
{
|
||
register ptr_t *newbuf; /* new block of user data */
|
||
register siz_t size; /* amount of memory to allocate */
|
||
|
||
size = count * length;
|
||
newbuf = malloc(size);
|
||
if (newbuf == NULL)
|
||
return(NULL);
|
||
|
||
bzero(newbuf, size);
|
||
return(newbuf);
|
||
}
|
||
|
||
|
||
/*
|
||
** CFREE -- Put unneeded memory on the free list
|
||
** ---------------------------------------------
|
||
*/
|
||
|
||
free_t
|
||
cfree(oldbuf)
|
||
ptr_t *oldbuf; /* old block of user data */
|
||
{
|
||
free_status = dealloc(oldbuf);
|
||
free_return(free_status);
|
||
}
|
||
|
||
/*
|
||
** VALLOC -- Allocate memory on a page boundary
|
||
** --------------------------------------------
|
||
**
|
||
** This is a wrapper for memalign to request memory that is
|
||
** aligned on a page boundary. In this implementation, this
|
||
** is the strictest alignment possible.
|
||
*/
|
||
|
||
ptr_t *
|
||
valloc(size)
|
||
siz_t size; /* amount of memory to allocate */
|
||
{
|
||
register ptr_t *newbuf; /* new block of user data */
|
||
siz_t align = PAGESIZE; /* alignment on page boundary */
|
||
|
||
newbuf = memalign(align, size);
|
||
return(newbuf);
|
||
}
|
||
|
||
|
||
/*
|
||
** VFREE -- Put unneeded memory on the free list
|
||
** ---------------------------------------------
|
||
*/
|
||
|
||
free_t
|
||
vfree(oldbuf)
|
||
ptr_t *oldbuf; /* old block of user data */
|
||
{
|
||
free_status = dealloc(oldbuf);
|
||
free_return(free_status);
|
||
}
|