/*_ mem.c */ /* Memory management package */ /* Written by Walter Bright */ #include #if MSDOS || __OS2__ || __NT__ || _WIN32 #include #else #include #include #include #endif #include #include #if __cplusplus #if __DMC__ #include #else #include #endif #endif #ifndef malloc #if __SC__ || __DMC__ || _MSC_VER #include #else #include #endif #endif #ifndef MEM_H #include "mem.h" #endif #ifndef MEM_NOMEMCOUNT #define MEM_NOMEMCOUNT 0 #endif #if !MEM_NONE #ifndef assert #include #endif #ifndef VAX11C #ifdef BSDUNIX #include #else #include #endif #else extern char *strcpy(),*memcpy(); extern int strlen(); #endif /* VAX11C */ int mem_inited = 0; /* != 0 if initialized */ static int mem_behavior = MEM_ABORTMSG; static int (*oom_fp)(void) = NULL; /* out-of-memory handler */ static int mem_count; /* # of allocs that haven't been free'd */ static int mem_scount; /* # of sallocs that haven't been free'd */ /* Determine where to send error messages */ #if _WINDLL void err_message(const char *,...); #define PRINT err_message( #elif MSDOS #define PRINT printf( /* stderr can't be redirected with MS-DOS */ #else #define ferr stderr #define PRINT fprintf(ferr, #endif /*******************************/ void mem_setexception(enum MEM_E flag,...) { va_list ap; typedef int (*fp_t)(void); mem_behavior = flag; va_start(ap,flag); oom_fp = (mem_behavior == MEM_CALLFP) ? va_arg(ap,fp_t) : 0; va_end(ap); #if MEM_DEBUG assert(0 <= flag && flag <= MEM_RETRY); #endif } /************************* * This is called when we're out of memory. * Returns: * 1: try again to allocate the memory * 0: give up and return NULL */ int mem_exception() { int behavior; behavior = mem_behavior; while (1) { switch (behavior) { case MEM_ABORTMSG: #if MSDOS || __OS2__ || __NT__ || _WIN32 /* Avoid linking in buffered I/O */ { static char msg[] = "Fatal error: out of memory\r\n"; write(1,msg,sizeof(msg) - 1); } #else PRINT "Fatal error: out of memory\n"); #endif /* FALL-THROUGH */ case MEM_ABORT: exit(EXIT_FAILURE); /* NOTREACHED */ case MEM_CALLFP: assert(oom_fp); behavior = (*oom_fp)(); break; case MEM_RETNULL: return 0; case MEM_RETRY: return 1; default: assert(0); } } } /****************************/ #if MEM_DEBUG #undef mem_strdup char *mem_strdup(const char *s) { return mem_strdup_debug(s,__FILE__,__LINE__); } char *mem_strdup_debug(const char *s,const char *file,int line) { char *p; p = s ? (char *) mem_malloc_debug((unsigned) strlen(s) + 1,file,line) : NULL; return p ? strcpy(p,s) : p; } #else char *mem_strdup(const char *s) { char *p; int len; if (s) { len = strlen(s) + 1; p = (char *) mem_malloc(len); if (p) return (char *)memcpy(p,s,len); } return NULL; } #endif /* MEM_DEBUG */ /************* C++ Implementation ***************/ #if __cplusplus && !MEM_NONE extern "C++" { /* Cause initialization and termination functions to be called */ #if 0 static struct cMemDebug { cMemDebug() { mem_init(); } ~cMemDebug() { mem_term(); } } dummy; #endif int __mem_line; char *__mem_file; /******************** */ #if __GNUC__ int (*_new_handler)(void); #else void (*_new_handler)(void); #endif /***************************** * Replacement for the standard C++ library operator new(). */ #if !MEM_NONEW #if __GNUC__ void * operator new(size_t size) #else #undef new void * __cdecl operator new(size_t size) #endif { void *p; while (1) { if (size == 0) size++; #if MEM_DEBUG assert(mem_inited); p = mem_malloc_debug(size,__mem_file,__mem_line); #else p = mem_malloc((unsigned)size); #endif if (p != NULL || _new_handler == NULL) break; (*_new_handler)(); } return p; } #if __GNUC__ void * operator new[](size_t size) #else void * __cdecl operator new[](size_t size) #endif { void *p; while (1) { if (size == 0) size++; #if MEM_DEBUG assert(mem_inited); p = mem_malloc_debug(size,__mem_file,__mem_line); #else p = mem_malloc((unsigned)size); #endif if (p != NULL || _new_handler == NULL) break; (*_new_handler)(); } return p; } /*********************** * Replacement for the standard C++ library operator delete(). */ #undef delete void __cdecl operator delete(void *p) { #if MEM_DEBUG assert(mem_inited); mem_free_debug(p,__mem_file,__mem_line); #else mem_free(p); #endif } void __cdecl operator delete[](void *p) { #if MEM_DEBUG assert(mem_inited); mem_free_debug(p,__mem_file,__mem_line); #else mem_free(p); #endif } #endif } #endif #if MEM_DEBUG static long mem_maxalloc; /* max # of bytes allocated */ static long mem_numalloc; /* current # of bytes allocated */ #define BEFOREVAL 0x4F464542 /* value to detect underrun */ #define AFTERVAL 0x45544641 /* value to detect overrun */ #if SUN || SUN386 static long afterval = AFTERVAL; /* so we can do &afterval */ #endif /* The following should be selected to give maximum probability that */ /* pointers loaded with these values will cause an obvious crash. On */ /* Unix machines, a large value will cause a segment fault. */ /* MALLOCVAL is the value to set malloc'd data to. */ #if MSDOS || __OS2__ || __NT__ || _WIN32 #define BADVAL 0xFF #define MALLOCVAL 0xEE #else #define BADVAL 0x7A #define MALLOCVAL 0xEE #endif /* Disable mapping macros */ #undef mem_malloc #undef mem_calloc #undef mem_realloc #undef mem_free /* Create a list of all alloc'ed pointers, retaining info about where */ /* each alloc came from. This is a real memory and speed hog, but who */ /* cares when you've got obscure pointer bugs. */ static struct mem_debug { struct mem_debug *Mnext; /* next in list */ struct mem_debug *Mprev; /* previous value in list */ const char *Mfile; /* filename of where allocated */ int Mline; /* line number of where allocated */ unsigned Mnbytes; /* size of the allocation */ unsigned long Mbeforeval; /* detect underrun of data */ char data[1]; /* the data actually allocated */ } mem_alloclist = { (struct mem_debug *) NULL, (struct mem_debug *) NULL, NULL, 11111, 0, BEFOREVAL, #if !(linux || __APPLE__ || __FreeBSD__ || __OpenBSD__ || __sun&&__SVR4) AFTERVAL #endif }; /* Determine allocation size of a mem_debug */ #define mem_debug_size(n) (sizeof(struct mem_debug) - 1 + (n) + sizeof(AFTERVAL)) /* Convert from a void *to a mem_debug struct. */ #define mem_ptrtodl(p) ((struct mem_debug *) ((char *)p - offsetof(struct mem_debug,data[0]))) /* Convert from a mem_debug struct to a mem_ptr. */ #define mem_dltoptr(dl) ((void *) &((dl)->data[0])) /***************************** * Set new value of file,line */ void mem_setnewfileline( void *ptr, const char *fil, int lin) { struct mem_debug *dl; dl = mem_ptrtodl(ptr); dl->Mfile = fil; dl->Mline = lin; } /**************************** * Print out struct mem_debug. */ static void mem_printdl(struct mem_debug *dl) { PRINT "alloc'd from file '%s' line %d nbytes %d ptr %p\n", dl->Mfile,dl->Mline,dl->Mnbytes,(long)mem_dltoptr(dl)); } /**************************** * Print out file and line number. */ static void mem_fillin(const char *fil, int lin) { PRINT "File '%s' line %d\n",fil,lin); #ifdef ferr fflush(ferr); #endif } /**************************** * If MEM_DEBUG is not on for some modules, these routines will get * called. */ void *mem_calloc(unsigned u) { return mem_calloc_debug(u,__FILE__,__LINE__); } void *mem_malloc(unsigned u) { return mem_malloc_debug(u,__FILE__,__LINE__); } void *mem_realloc(void *p, unsigned u) { return mem_realloc_debug(p,u,__FILE__,__LINE__); } void mem_free(void *p) { mem_free_debug(p,__FILE__,__LINE__); } /**************************/ void mem_freefp(void *p) { mem_free(p); } /*********************** * Debug versions of mem_calloc(), mem_free() and mem_realloc(). */ void *mem_malloc_debug(unsigned n, const char *fil, int lin) { void *p; p = mem_calloc_debug(n,fil,lin); if (p) memset(p,MALLOCVAL,n); return p; } void *mem_calloc_debug(unsigned n, const char *fil, int lin) { struct mem_debug *dl; do dl = (struct mem_debug *) calloc(mem_debug_size(n),1); while (dl == NULL && mem_exception()); if (dl == NULL) return NULL; dl->Mfile = fil; dl->Mline = lin; dl->Mnbytes = n; dl->Mbeforeval = BEFOREVAL; #if SUN || SUN386 /* bus error if we store a long at an odd address */ memcpy(&(dl->data[n]),&afterval,sizeof(AFTERVAL)); #else *(long *) &(dl->data[n]) = AFTERVAL; #endif /* Add dl to start of allocation list */ dl->Mnext = mem_alloclist.Mnext; dl->Mprev = &mem_alloclist; mem_alloclist.Mnext = dl; if (dl->Mnext != NULL) dl->Mnext->Mprev = dl; mem_count++; mem_numalloc += n; if (mem_numalloc > mem_maxalloc) mem_maxalloc = mem_numalloc; return mem_dltoptr(dl); } void mem_free_debug(void *ptr, const char *fil, int lin) { struct mem_debug *dl; if (ptr == NULL) return; if (mem_count <= 0) { PRINT "More frees than allocs at "); goto err; } dl = mem_ptrtodl(ptr); if (dl->Mbeforeval != BEFOREVAL) { PRINT "Pointer x%lx underrun\n",(long)ptr); PRINT "'%s'(%d)\n",fil,lin); goto err2; } #if SUN || SUN386 /* Bus error if we read a long from an odd address */ if (memcmp(&dl->data[dl->Mnbytes],&afterval,sizeof(AFTERVAL)) != 0) #else if (*(long *) &dl->data[dl->Mnbytes] != AFTERVAL) #endif { PRINT "Pointer x%lx overrun\n",(long)ptr); goto err2; } mem_numalloc -= dl->Mnbytes; if (mem_numalloc < 0) { PRINT "error: mem_numalloc = %ld, dl->Mnbytes = %d\n", mem_numalloc,dl->Mnbytes); goto err2; } /* Remove dl from linked list */ if (dl->Mprev) dl->Mprev->Mnext = dl->Mnext; if (dl->Mnext) dl->Mnext->Mprev = dl->Mprev; /* Stomp on the freed storage to help detect references */ /* after the storage was freed. */ memset((void *) dl,BADVAL,sizeof(*dl) + dl->Mnbytes); mem_count--; free((void *) dl); return; err2: mem_printdl(dl); err: PRINT "free'd from "); mem_fillin(fil,lin); assert(0); /* NOTREACHED */ } /******************* * Debug version of mem_realloc(). */ void *mem_realloc_debug(void *oldp, unsigned n, const char *fil, int lin) { void *p; struct mem_debug *dl; if (n == 0) { mem_free_debug(oldp,fil,lin); p = NULL; } else if (oldp == NULL) p = mem_malloc_debug(n,fil,lin); else { p = mem_malloc_debug(n,fil,lin); if (p != NULL) { dl = mem_ptrtodl(oldp); if (dl->Mnbytes < n) n = dl->Mnbytes; memcpy(p,oldp,n); mem_free_debug(oldp,fil,lin); } } return p; } /***************************/ static void mem_checkdl(struct mem_debug *dl) { void *p; #if (__SC__ || __DMC__) && !_WIN32 unsigned u; /* Take advantage of fact that SC's allocator stores the size of the * alloc in the unsigned immediately preceding the allocation. */ u = ((unsigned *)dl)[-1] - sizeof(unsigned); assert((u & (sizeof(unsigned) - 1)) == 0 && u >= mem_debug_size(dl->Mnbytes)); #endif p = mem_dltoptr(dl); if (dl->Mbeforeval != BEFOREVAL) { PRINT "Pointer x%lx underrun\n",(long)p); goto err2; } #if SUN || SUN386 /* Bus error if we read a long from an odd address */ if (memcmp(&dl->data[dl->Mnbytes],&afterval,sizeof(AFTERVAL)) != 0) #else if (*(long *) &dl->data[dl->Mnbytes] != AFTERVAL) #endif { PRINT "Pointer x%lx overrun\n",(long)p); goto err2; } return; err2: mem_printdl(dl); assert(0); } /***************************/ void mem_check() { register struct mem_debug *dl; #if (__SC__ || _MSC_VER) && !defined(malloc) int i; i = _heapset(0xF4); assert(i == _HEAPOK); #endif for (dl = mem_alloclist.Mnext; dl != NULL; dl = dl->Mnext) mem_checkdl(dl); } /***************************/ void mem_checkptr(void *p) { register struct mem_debug *dl; for (dl = mem_alloclist.Mnext; dl != NULL; dl = dl->Mnext) { if (p >= (void *) &(dl->data[0]) && p < (void *)((char *)dl + sizeof(struct mem_debug)-1 + dl->Mnbytes)) goto L1; } assert(0); L1: mem_checkdl(dl); } #else /***************************/ void *mem_malloc(unsigned numbytes) { void *p; if (numbytes == 0) return NULL; while (1) { p = malloc(numbytes); if (p == NULL) { if (mem_exception()) continue; } #if !MEM_NOMEMCOUNT else mem_count++; #endif break; } /*printf("malloc(%d) = x%lx, mem_count = %d\n",numbytes,p,mem_count);*/ return p; } /***************************/ void *mem_calloc(unsigned numbytes) { void *p; if (numbytes == 0) return NULL; while (1) { p = calloc(numbytes,1); if (p == NULL) { if (mem_exception()) continue; } #if !MEM_NOMEMCOUNT else mem_count++; #endif break; } /*printf("calloc(%d) = x%lx, mem_count = %d\n",numbytes,p,mem_count);*/ return p; } /***************************/ void *mem_realloc(void *oldmem_ptr,unsigned newnumbytes) { void *p; if (oldmem_ptr == NULL) p = mem_malloc(newnumbytes); else if (newnumbytes == 0) { mem_free(oldmem_ptr); p = NULL; } else { do p = realloc(oldmem_ptr,newnumbytes); while (p == NULL && mem_exception()); } /*printf("realloc(x%lx,%d) = x%lx, mem_count = %d\n",oldmem_ptr,newnumbytes,p,mem_count);*/ return p; } /***************************/ void mem_free(void *ptr) { /*printf("free(x%lx) mem_count=%d\n",ptr,mem_count);*/ if (ptr != NULL) { #if !MEM_NOMEMCOUNT assert(mem_count != 0); mem_count--; #endif free(ptr); } } /***************************/ /* This is our low-rent fast storage allocator */ static char *heap; static size_t heapleft; /***************************/ #if 0 && __SC__ && __INTSIZE == 4 && __I86__ && !_DEBUG_TRACE && _WIN32 && (SCC || SCPP || JAVA) __declspec(naked) void *mem_fmalloc(unsigned numbytes) { __asm { mov EDX,4[ESP] mov EAX,heap add EDX,3 mov ECX,heapleft and EDX,~3 je L5A cmp EDX,ECX ja L2D sub ECX,EDX add EDX,EAX mov heapleft,ECX mov heap,EDX ret 4 L2D: push EBX mov EBX,EDX // add EDX,03FFFh // and EDX,~03FFFh add EDX,03C00h mov heapleft,EDX L3D: push heapleft call mem_malloc test EAX,EAX mov heap,EAX jne L18 call mem_exception test EAX,EAX jne L3D pop EBX L5A: xor EAX,EAX ret 4 L18: add heap,EBX sub heapleft,EBX pop EBX ret 4 } } #else void *mem_fmalloc(unsigned numbytes) { void *p; //printf("fmalloc(%d)\n",numbytes); #if defined(__llvm__) && (defined(__GNUC__) || defined(__clang__)) // LLVM-GCC and Clang assume some types, notably elem (see DMD issue 6215), // to be 16-byte aligned. Because we do not have any type information // available here, we have to 16 byte-align everything. numbytes = (numbytes + 0xF) & ~0xF; #else if (sizeof(size_t) == 2) numbytes = (numbytes + 1) & ~1; /* word align */ else numbytes = (numbytes + 3) & ~3; /* dword align */ #endif /* This ugly flow-of-control is so that the most common case drops straight through. */ if (!numbytes) return NULL; if (numbytes <= heapleft) { L2: p = (void *)heap; heap += numbytes; heapleft -= numbytes; return p; } #if 1 heapleft = numbytes + 0x3C00; if (heapleft >= 16372) heapleft = numbytes; #elif _WIN32 heapleft = (numbytes + 0x3FFF) & ~0x3FFF; /* round to next boundary */ #else heapleft = 0x3F00; assert(numbytes <= heapleft); #endif L1: heap = (char *)malloc(heapleft); if (!heap) { if (mem_exception()) goto L1; return NULL; } goto L2; } #endif /***************************/ void *mem_fcalloc(unsigned numbytes) { void *p; p = mem_fmalloc(numbytes); return p ? memset(p,0,numbytes) : p; } /***************************/ char *mem_fstrdup(const char *s) { char *p; int len; if (s) { len = strlen(s) + 1; p = (char *) mem_fmalloc(len); if (p) return (char *)memcpy(p,s,len); } return NULL; } #endif /***************************/ void mem_init() { if (mem_inited == 0) { mem_count = 0; mem_scount = 0; oom_fp = NULL; mem_behavior = MEM_ABORTMSG; #if MEM_DEBUG mem_numalloc = 0; mem_maxalloc = 0; mem_alloclist.Mnext = NULL; #if linux || __APPLE__ || __FreeBSD__ || __OpenBSD__ || __sun&&__SVR4 *(long *) &(mem_alloclist.data[0]) = AFTERVAL; #endif #endif #if (__ZTC__ || __SC__ || __DMC__) && !defined(malloc) free(malloc(1)); /* initialize storage allocator */ #endif #if MEM_DEBUG && (__SC__ || _MSC_VER) && !defined(malloc) { int i; i = _heapset(0xF4); assert(i == _HEAPOK); } #endif } mem_inited++; } /***************************/ void mem_term() { if (mem_inited) { #if MEM_DEBUG struct mem_debug *dl; for (dl = mem_alloclist.Mnext; dl; dl = dl->Mnext) { PRINT "Unfreed pointer: "); mem_printdl(dl); } #if 0 PRINT "Max amount ever allocated == %ld bytes\n", mem_maxalloc); #endif #if (__SC__ || _MSC_VER) && !defined(malloc) { int i; i = _heapset(0xF4); assert(i == _HEAPOK); } #endif #else if (mem_count) PRINT "%d unfreed items\n",mem_count); if (mem_scount) PRINT "%d unfreed s items\n",mem_scount); #endif /* MEM_DEBUG */ assert(mem_count == 0 && mem_scount == 0); } mem_inited = 0; } #endif /* !MEM_NONE */