/**********************************************************/ /* /* This was file READ_ME /* /**********************************************************/ /* PROGRAM malloc(), free(), realloc() AUTHOR Dick Grune, Free University, Amsterdam Modified by Ceriel Jacobs, Free University, Amsterdam, to make it faster VERSION $Header: /cvsup/minix/src/commands/ash/test/malloc.c,v 1.1.1.1 2005/04/21 14:54:10 beng Exp $ DESCRIPTION This is an independent rewrite of the malloc/free package; it is fast and efficient. Free blocks are kept in doubly linked lists, list N holding blocks with sizes between 2**N and 2**(N+1)-1. Consequently neither malloc nor free have to do any searching: the cost of a call of malloc() (or free()) is constant, however many blocks you have got. If you switch on the NON_STANDARD macro (see param.h) every block costs 2 pointers overhead (otherwise it's 4). */ /* There is an organisational problem here: during devellopment I want the package divided into modules, which implies external names for the communication. The only external names I want in the finished product are malloc, realloc and free. This requires some hanky-panky. */ /**********************************************************/ /* /* This was file size_type.h /* /**********************************************************/ #if _EM_WSIZE == _EM_PSIZE typedef unsigned int size_type; #elif _EM_LSIZE == _EM_PSIZE typedef unsigned long size_type; #else #error funny pointer size #endif #include #include /**********************************************************/ /* /* This was file param.h /* /**********************************************************/ /* $Header: /cvsup/minix/src/commands/ash/test/malloc.c,v 1.1.1.1 2005/04/21 14:54:10 beng Exp $ */ /* * (c) copyright 1987 by the Vrije Universiteit, Amsterdam, The Netherlands. * See the copyright notice in the ACK home directory, in the file "Copyright". */ # undef NON_STANDARD /* If defined, the contents of a block will NOT be left undisturbed after it is freed, as opposed to what it says in the manual (malloc(2)). Setting this option reduces the memory overhead considerably. I personally consider the specified behaviour an artefact of the original implementation. */ # define ASSERT /* If defined, some inexpensive tests will be made to ensure the correctness of some sensitive data. It often turns an uncontrolled crash into a controlled one. */ # define CHECK /* If defined, extensive and expensive tests will be done, inculding a checksum on the mallinks (chunk information blocks). The resulting information will be printed on a file called mal.out . Additionally a function maldump(n) int n; will be defined, which will dump pertinent info in pseudo-readable form; it aborts afterwards if n != 0. */ # undef EXTERN /* If defined, all static names will become extern, which is a help in using adb(1) or prof(1) */ # define STORE /* If defined, separate free lists will be kept of chunks with small sizes, to speed things up a little. */ # undef SYSTEM /* If defined, the system module is used. Otherwise, "sbrk" is called directly. */ #define ALIGNMENT 8 /* alignment common to all types */ #define LOG_MIN_SIZE 3 #define LOG_MAX_SIZE 24 /**********************************************************/ /* /* This was file impl.h /* /**********************************************************/ /* $Header: /cvsup/minix/src/commands/ash/test/malloc.c,v 1.1.1.1 2005/04/21 14:54:10 beng Exp $ */ /* * (c) copyright 1987 by the Vrije Universiteit, Amsterdam, The Netherlands. * See the copyright notice in the ACK home directory, in the file "Copyright". */ /* This file essentially describes how the mallink info block is implemented. */ #define MIN_SIZE (1< #include /* Malloc space is traversed by N doubly-linked lists of chunks, each containing a couple of house-keeping data addressed as a 'mallink' and a piece of useful space, called the block. The N lists are accessed through their starting pointers in free_list[]. Free_list[n] points to a list of chunks between 2**(n+LOG_MIN_SIZE) and 2**(n+LOG_MIN_SIZE+1)-1, which means that the smallest chunk is 2**LOG_MIN_SIZE (== MIN_SIZE). */ #ifdef SYSTEM #include #define SBRK sys_break #else #define SBRK _sbrk #define ILL_BREAK (void *)(-1) /* funny failure value */ #endif extern void *SBRK(int incr); #ifdef STORE #define MAX_STORE 32 private do_free(mallink *ml), sell_out(void); privatedata mallink *store[MAX_STORE]; #endif /* STORE */ void *privious_free= (void *)-1; void * malloc(register size_t n) {check_mallinks("malloc entry");{ register mallink *ml; register int min_class; void *tmp; { static int reent= 0; if (!reent) { reent++; printf("malloc\n"); reent--; } } privious_free= (void *)-1; if (n == 0) { return NULL; } if (n < MIN_SIZE) n = align(MIN_SIZE); else n = align(n); #ifdef STORE if (n <= MAX_STORE*MIN_SIZE) { /* look in the store first */ register mallink **stp = &store[(n >> LOG_MIN_SIZE) - 1]; if (ml = *stp) { *stp = log_next_of(ml); set_store(ml, 0); check_mallinks("malloc fast exit"); assert(! in_store(ml)); tmp= block_of_mallink(ml); { static int reent= 0; if (!reent) { reent++; printf("= 0x%x\n", tmp);reent--; } } return tmp; } } #endif /* STORE */ check_work_empty("malloc, entry"); /* Acquire a chunk of at least size n if at all possible; Try everything. */ { /* Inline substitution of "smallest". */ register size_t n1 = n; assert(n1 < (1L << LOG_MAX_SIZE)); min_class = 0; do { n1 >>= 1; min_class++; } while (n1 >= MIN_SIZE); } if (min_class >= MAX_FLIST) return NULL; /* we don't deal in blocks that big */ ml = first_present(min_class); if (ml == MAL_NULL) { /* Try and extend */ register void *p; #define GRABSIZE 4096 /* Power of 2 */ register size_t req = ((MIN_SIZE<= 0) p = SBRK((int)req); } if (p == ILL_BREAK) { /* Now this is bad. The system will not give us more memory. We can only liquidate our store and hope it helps. */ #ifdef STORE sell_out(); ml = first_present(min_class); if (ml == MAL_NULL) { #endif /* STORE */ /* In this emergency we try to locate a suitable chunk in the free_list just below the safe one; some of these chunks may fit the job. */ ml = search_free_list(min_class - 1, n); if (!ml) /* really out of space */ return NULL; started_working_on(ml); unlink_free_chunk(ml); check_mallinks("suitable_chunk, forced"); #ifdef STORE } else started_working_on(ml); #endif /* STORE */ } else { assert((size_type)p == align((size_type)p)); ml = create_chunk(p, req); } check_mallinks("suitable_chunk, extended"); } else started_working_on(ml); /* we have a chunk */ set_free(ml, 0); calc_checksum(ml); check_mallinks("suitable_chunk, removed"); n += mallink_size(); if (n + MIN_SIZE <= size_of(ml)) { truncate(ml, n); } stopped_working_on(ml); check_mallinks("malloc exit"); check_work_empty("malloc exit"); #ifdef STORE assert(! in_store(ml)); #endif tmp= block_of_mallink(ml); { static int reent= 0; if (!reent) { reent++; printf("= 0x%x\n", tmp);reent--; } } return tmp; }} void free(void *addr) {check_mallinks("free entry");{ register mallink *ml; printf("free 0x%x\n", addr); if (privious_free == addr) { fflush(stdout); fflush(stderr); abort(); } privious_free= addr; if (addr == NULL) { check_mallinks("free(0) very fast exit"); return; } ml = mallink_of_block(addr); #ifdef STORE if (free_of(ml) || in_store(ml)) return; /* user frees free block */ if (size_of(ml) <= MAX_STORE*MIN_SIZE) { /* return to store */ mallink **stp = &store[(size_of(ml) >> LOG_MIN_SIZE) - 1]; set_log_next(ml, *stp); *stp = ml; set_store(ml, 1); calc_checksum(ml); check_mallinks("free fast exit"); } else { do_free(ml); check_mallinks("free exit"); } }} private do_free(register mallink *ml) {{ #endif #ifndef STORE if (free_of(ml)) return; #endif /* STORE */ started_working_on(ml); set_free(ml, 1); calc_checksum(ml); if (! last_mallink(ml)) { register mallink *next = phys_next_of(ml); if (free_of(next)) coalesce_forw(ml, next); } if (! first_mallink(ml)) { register mallink *prev = phys_prev_of(ml); if (free_of(prev)) { coalesce_backw(ml, prev); ml = prev; } } link_free_chunk(ml); stopped_working_on(ml); check_work_empty("free"); /* Compile-time checks on param.h */ switch (0) { case MIN_SIZE < OFF_SET * sizeof(mallink): break; case 1: break; /* If this statement does not compile due to duplicate case entry, the minimum size block cannot hold the links for the free blocks. Either raise LOG_MIN_SIZE or switch off NON_STANDARD. */ } switch(0) { case sizeof(void *) != sizeof(size_type): break; case 1: break; /* If this statement does not compile due to duplicate case entry, size_type is not defined correctly. Redefine and compile again. */ } }} void * realloc(void *addr, register size_t n) {check_mallinks("realloc entry");{ register mallink *ml, *ph_next; register size_type size; printf("realloc 0x%x, %d\n", addr, n); if (addr == NULL) { /* Behave like most Unix realloc's when handed a null-pointer */ return malloc(n); } if (n == 0) { free(addr); return NULL; } ml = mallink_of_block(addr); if (n < MIN_SIZE) n = align(MIN_SIZE); else n = align(n); #ifdef STORE if (in_store(ml)) { register mallink *stp = store[(size_of(ml) >> LOG_MIN_SIZE) - 1]; mallink *stp1 = NULL; while (ml != stp) { stp1 = stp; stp = log_next_of(stp); } stp = log_next_of(stp); if (! stp1) store[(size_of(ml) >> LOG_MIN_SIZE) - 1] = stp; else set_log_next(stp1, stp); set_store(ml, 0); calc_checksum(ml); } #endif if (free_of(ml)) { unlink_free_chunk(ml); set_free(ml, 0); /* user reallocs free block */ } started_working_on(ml); size = size_of(ml); if ( /* we can simplify the problem by adding the next chunk: */ n > size && !last_mallink(ml) && (ph_next = phys_next_of(ml), free_of(ph_next)) && n <= size + mallink_size() + size_of(ph_next) ) { /* add in the physically next chunk */ unlink_free_chunk(ph_next); combine_chunks(ml, ph_next); size = size_of(ml); check_mallinks("realloc, combining"); } if (n > size) { /* this didn't help */ void *new; register char *l1, *l2 = addr; stopped_working_on(ml); if (!(new = l1 = malloc(n))) return NULL; /* no way */ while (size--) *l1++ = *l2++; free(addr); check_work_empty("mv_realloc"); #ifdef STORE assert(! in_store(mallink_of_block(new))); #endif return new; } /* it helped, but maybe too well */ n += mallink_size(); if (n + MIN_SIZE <= size_of(ml)) { truncate(ml, n); } stopped_working_on(ml); check_mallinks("realloc exit"); check_work_empty("realloc"); #ifdef STORE assert(! in_store(ml)); #endif return addr; }} void * calloc(size_t nmemb, size_t size) {check_mallinks("calloc entry");{ long *l1, *l2; size_t n; printf("calloc\n"); if (size == 0) return NULL; if (nmemb == 0) return NULL; /* Check for overflow on the multiplication. The peephole-optimizer * will eliminate all but one of the possibilities. */ if (sizeof(size_t) == sizeof(int)) { if (UINT_MAX / size < nmemb) return NULL; } else if (sizeof(size_t) == sizeof(long)) { if (ULONG_MAX / size < nmemb) return NULL; } else return NULL; /* can't happen, can it ? */ n = size * nmemb; if (n < MIN_SIZE) n = align(MIN_SIZE); else n = align(n); if (n >= (1L << LOG_MAX_SIZE)) return NULL; l1 = (long *) malloc(n); l2 = l1 + (n / sizeof(long)); /* n is at least long aligned */ while ( l2 != l1 ) *--l2 = 0; check_mallinks("calloc exit"); check_work_empty("calloc exit"); return (void *)l1; }} /* Auxiliary routines */ #ifdef STORE private sell_out(void) { /* Frees all block in store. */ register mallink **stp; for (stp = &store[0]; stp < &store[MAX_STORE]; stp++) { register mallink *ml = *stp; while (ml) { *stp = log_next_of(ml); set_store(ml, 0); do_free(ml); ml = *stp; } } } #endif /* STORE */ #ifdef ASSERT private m_assert(const char *fn, int ln) { char ch; while (*fn) write(2, fn++, 1); write(2, ": malloc assert failed in line ", 31); ch = (ln / 100) + '0'; write(2, &ch, 1); ln %= 100; ch = (ln / 10) + '0'; write(2, &ch, 1); ln %= 10; ch = (ln / 1) + '0'; write(2, &ch, 1); write(2, "\n", 1); maldump(1); } #endif /* ASSERT */ /**********************************************************/ /* /* This was file log.c /* /**********************************************************/ /* $Header: /cvsup/minix/src/commands/ash/test/malloc.c,v 1.1.1.1 2005/04/21 14:54:10 beng Exp $ */ /* * (c) copyright 1987 by the Vrije Universiteit, Amsterdam, The Netherlands. * See the copyright notice in the ACK home directory, in the file "Copyright". */ /* Logical manipulations. The chunks are properly chained in the physical chain. */ privatedata mallink *free_list[MAX_FLIST]; private link_free_chunk(register mallink *ml) { /* The free chunk ml is inserted in its proper logical chain. */ register mallink **mlp = &free_list[-1]; register size_type n = size_of(ml); register mallink *ml1; assert(n < (1L << LOG_MAX_SIZE)); do { n >>= 1; mlp++; } while (n >= MIN_SIZE); ml1 = *mlp; set_log_prev(ml, MAL_NULL); set_log_next(ml, ml1); calc_checksum(ml); if (ml1) { /* link backwards */ set_log_prev(ml1, ml); calc_checksum(ml1); } *mlp = ml; } private unlink_free_chunk(register mallink *ml) { /* Unlinks a free chunk from (the middle of) the logical chain. */ register mallink *next = log_next_of(ml); register mallink *prev = log_prev_of(ml); if (!prev) { /* it is the first in the chain */ register mallink **mlp = &free_list[-1]; register size_type n = size_of(ml); assert(n < (1L << LOG_MAX_SIZE)); do { n >>= 1; mlp++; } while (n >= MIN_SIZE); *mlp = next; } else { set_log_next(prev, next); calc_checksum(prev); } if (next) { set_log_prev(next, prev); calc_checksum(next); } } private mallink * search_free_list(int class, size_t n) { /* Searches the free_list[class] for a chunk of at least size n; since it is searching a slightly undersized list, such a block may not be there. */ register mallink *ml; for (ml = free_list[class]; ml; ml = log_next_of(ml)) if (size_of(ml) >= n) return ml; return MAL_NULL; /* nothing found */ } private mallink * first_present(int class) { /* Find the index i in free_list[] such that: i >= class && free_list[i] != MAL_NULL. Return MAL_NULL if no such i exists; Otherwise, return the first block of this list, after unlinking it. */ register mallink **mlp, *ml; for (mlp = &free_list[class]; mlp < &free_list[MAX_FLIST]; mlp++) { if ((ml = *mlp) != MAL_NULL) { *mlp = log_next_of(ml); /* may be MAL_NULL */ if (*mlp) { /* unhook backward link */ set_log_prev(*mlp, MAL_NULL); calc_checksum(*mlp); } return ml; } } return MAL_NULL; } #ifdef CHECK private mallink * free_list_entry(int i) { /* To allow maldump.c access to log.c's private data. */ return free_list[i]; } #endif /* CHECK */ /**********************************************************/ /* /* This was file phys.c /* /**********************************************************/ /* $Header: /cvsup/minix/src/commands/ash/test/malloc.c,v 1.1.1.1 2005/04/21 14:54:10 beng Exp $ */ /* * (c) copyright 1987 by the Vrije Universiteit, Amsterdam, The Netherlands. * See the copyright notice in the ACK home directory, in the file "Copyright". */ #include /* Physical manipulations. The blocks concerned are not in any logical chain. */ private mallink * create_chunk(void *p, size_t n) { /* The newly acquired piece of memory at p, of length n, is turned into a free chunk, properly chained in the physical chain. The address of the chunk is returned. */ register mallink *ml; /* All of malloc memory is followed by a virtual chunk, the mallink of which starts mallink_size() bytes past the last byte in memory. Its use is prevented by testing for ml == ml_last first. */ register mallink *last = ml_last; assert(!last || p == (char *)phys_next_of(last) - mallink_size()); ml = (mallink *)((char *)p + mallink_size()); /* bump ml */ new_mallink(ml); started_working_on(ml); set_free(ml, 1); set_phys_prev(ml, last); ml_last = ml; set_phys_next(ml, (mallink *)((char *)ml + n)); calc_checksum(ml); assert(size_of(ml) + mallink_size() == n); if (last && free_of(last)) { coalesce_backw(ml, last); ml = last; } check_mallinks("create_chunk, phys. linked"); return ml; } private truncate(register mallink *ml, size_t size) { /* The chunk ml is truncated. The chunk at ml is split in two. The remaining part is then freed. */ register mallink *new = (mallink *)((char *)ml + size); register mallink *ph_next = phys_next_of(ml); new_mallink(new); set_free(new, 1); set_phys_prev(new, ml); set_phys_next(new, ph_next); calc_checksum(new); if (! last_mallink(ml)) { set_phys_prev(ph_next, new); calc_checksum(ph_next); if (free_of(ph_next)) coalesce_forw(new, ph_next); } else ml_last = new; set_phys_next(ml, new); calc_checksum(ml); started_working_on(new); link_free_chunk(new); stopped_working_on(new); check_mallinks("truncate"); } private combine_chunks(register mallink *ml1, register mallink *ml2) { /* The chunks ml1 and ml2 are combined. */ register mallink *ml3 = phys_next_of(ml2); set_phys_next(ml1, ml3); calc_checksum(ml1); if (!last_mallink(ml2)) { set_phys_prev(ml3, ml1); calc_checksum(ml3); } if (ml_last == ml2) ml_last = ml1; } /**********************************************************/ /* /* This was file check.c /* /**********************************************************/ /* $Header: /cvsup/minix/src/commands/ash/test/malloc.c,v 1.1.1.1 2005/04/21 14:54:10 beng Exp $ */ /* * (c) copyright 1987 by the Vrije Universiteit, Amsterdam, The Netherlands. * See the copyright notice in the ACK home directory, in the file "Copyright". */ #include #ifdef CHECK /* otherwise this whole file is skipped */ /* ??? check these later */ private acquire_malout(void), check_ml_last(const char *s); private dump_all_mallinks(void), dump_free_list(int i); private dump_mallink(const char *s, mallink *ml), print_loop(mallink *ml); private working_on(mallink *ml); private size_type checksum(mallink *ml); static FILE *malout; private mallink *free_list_entry(int i); #define for_free_list(i,p) \ for (p = free_list_entry(i); p; p = log_next_of(p)) #define for_all_mallinks(ml) /* backwards! */ \ for (ml = ml_last; ml; \ ml = first_mallink(ml) ? MAL_NULL : phys_prev_of(ml)) /* Maldump */ static int pr_cnt = 0; maldump(int n) { /* Dump pertinent info in pseudo-readable format; abort afterwards if n != 0. */ static int dumping = 0; int i; if (dumping) return; dumping++; acquire_malout(); fprintf(malout, ">>>>>>>>>>>>>>>> DUMP OF ALL MALLINKS <<<<<<<<<<<<<<<<"); fprintf(malout, " ml_last = %p\n", ml_last); if (++pr_cnt == 100) pr_cnt = 0; dump_all_mallinks(); fprintf(malout, ">>>>>>>>>>>>>>>> DUMP OF FREE_LISTS <<<<<<<<<<<<<<<<\n"); if (++pr_cnt == 100) pr_cnt = 0; for (i = 0; i < MAX_FLIST; i++) dump_free_list(i); fprintf(malout, ">>>>>>>>>>>>>>>> END OF DUMP <<<<<<<<<<<<<<<<\n"); fclose(malout); dumping--; if (n) abort(); } private acquire_malout(void) { static char buf[BUFSIZ]; if (!malout) { malout = freopen("mal.out", "w", stderr); setbuf(malout, buf); } } private dump_all_mallinks(void) { mallink *ml; for_all_mallinks (ml) { if (print_loop(ml)) return; dump_mallink((char *)0, ml); } } private dump_free_list(int i) { mallink *ml = free_list_entry(i); if (!ml) return; fprintf(malout, "%2d: ", i); for_free_list(i, ml) { if (print_loop(ml)) return; fprintf(malout, "%p ", ml); } fprintf(malout, "<\n"); } private int print_loop(mallink *ml) { if (print_of(ml) == pr_cnt) { fprintf(malout, "... PRINT LOOP\n"); return 1; } set_print(ml, pr_cnt); return 0; } private dump_mallink(const char *s, mallink *ml) { acquire_malout(); if (s) fprintf(malout, "%s: ", s); fprintf(malout, "@: %p;", ml); if (ml && checksum_of(ml) != checksum(ml)) fprintf(malout, ">>>> CORRUPTED <<<<"); if (!ml) { fprintf(malout, "\n"); return; } if (free_of(ml)) { fprintf(malout, " l_p: %p;", _log_prev_of(ml)); fprintf(malout, " l_n: %p;", _log_next_of(ml)); } fprintf(malout, " p_s: %p;", prev_size_of(ml)); fprintf(malout, " t_s: %p;", _this_size_of(ml)); fprintf(malout, " sz: %lu;", (unsigned long) size_of(ml)); fprintf(malout, " fr: %d;", free_of(ml)); fprintf(malout, "\n"); } /* Check_mallinks() checks the total data structure as accessible through free_list[] and ml_last. All check_sums should be OK, except those held in the small array off_colour. This is a trick to allow to continue checking even when a few mallinks are temporarily out of order. Check_mallinks() tests for a lot of internal consistency. */ /* Some arbitrary constants */ #define IN_ML_LAST 93 #define IN_FREE_LIST 57 /* and in ml_last */ #define CLEAR 21 #define VRIJ 1 #define BEZET 2 private check_mallinks(const char *s) { mallink *ml; size_type size; int i; char stat; check_ml_last(s); stat = BEZET; for_all_mallinks(ml) { if (checksum_of(ml) != checksum(ml)) Error("mallink info at %p corrupted", s, ml); if (working_on(ml)) { stat = BEZET; continue; } if ( !last_mallink(ml) && phys_prev_of(phys_next_of(ml)) != ml ) Error("upward chain bad at %p", s, ml); if ( !first_mallink(ml) && phys_next_of(phys_prev_of(ml)) != ml ) Error("downward chain bad at %p", s, ml); if (free_of(ml)) { if (stat == VRIJ) Error("free mallink at %p follows free mallink", s, ml); stat = VRIJ; } else stat = BEZET; set_mark(ml, IN_ML_LAST); } for (i = 0, size = MIN_SIZE; i < MAX_FLIST; i++, size *= 2) { for_free_list(i, ml) { if (working_on(ml)) continue; if (!free_of(ml)) Error("occupied mallink %p occurs in free_list", s, ml); switch (mark_of(ml)) { case IN_ML_LAST: set_mark(ml, IN_FREE_LIST); break; case IN_FREE_LIST: Error("mallink %p occurs in 2 free_lists", s, ml); default: Error("unknown mallink %p in free_list", s, ml); } if (size_of(ml) < size) Error("size of mallink %p too small", s, ml); if (size_of(ml) >= 2*size) Error("size of mallink %p too large", s, ml); } } for_all_mallinks (ml) { if (working_on(ml)) continue; if (free_of(ml) && mark_of(ml) != IN_FREE_LIST) Error("free mallink %p is in no free_list", s, ml); set_mark(ml, CLEAR); } } private check_ml_last(const char *s) { if (ml_last && _this_size_of(ml_last) == 0) Error("size of ml_last == 0, at %p", s, ml_last); } private size_type checksum(mallink *ml) { size_type sum = 0; if (free_of(ml)) { sum += (size_type)_log_prev_of(ml); sum += (size_type)_log_next_of(ml); } sum += (size_type)prev_size_of(ml); sum += (size_type)_this_size_of(ml); return sum; } private calc_checksum(mallink *ml) { set_checksum(ml, checksum(ml)); } #define N_COLOUR 10 static mallink *off_colour[N_COLOUR]; private started_working_on(mallink *ml) { int i; for (i = 0; i < N_COLOUR; i++) if (off_colour[i] == MAL_NULL) { off_colour[i] = ml; return; } Error("out of off_colour array at %p", "started_working_on", ml); } private stopped_working_on(mallink *ml) { int i; for (i = 0; i < N_COLOUR; i++) if (off_colour[i] == ml) { off_colour[i] = MAL_NULL; return; } Error("stopped working on mallink %p", "stopped_working_on", ml); } private int working_on(mallink *ml) { int i; for (i = 0; i < N_COLOUR; i++) if (off_colour[i] == ml) return 1; return 0; } private check_work_empty(const char *s) { int i; int cnt = 0; for (i = 0; i < N_COLOUR; i++) if (off_colour[i] != MAL_NULL) cnt++; if (cnt != 0) Error("off_colour not empty", s, MAL_NULL); } private int Error(const char *fmt, const char *s, mallink *ml) { static int already_called = 0; if (already_called++) return 0; setbuf(stdout, (char *) 0); printf("%s: ", s); printf(fmt, (long)ml); printf("\n"); acquire_malout(); fprintf(malout, "%s: ", s); fprintf(malout, fmt, (long)ml); fprintf(malout, "\n"); fflush(stdout); maldump(1); return 0; /* to satisfy lint */ } #endif /* CHECK */