1 /* $XConsortium: t1malloc.c,v 1.5 93/09/10 10:48:21 rws Exp $ */
2 /* Copyright International Business Machines, Corp. 1991
3 * All Rights Reserved
4 * Copyright Lexmark International, Inc. 1991
5 * All Rights Reserved
6 *
7 * License to use, copy, modify, and distribute this software and its
8 * documentation for any purpose and without fee is hereby granted,
9 * provided that the above copyright notice appear in all copies and that
10 * both that copyright notice and this permission notice appear in
11 * supporting documentation, and that the name of IBM or Lexmark not be
12 * used in advertising or publicity pertaining to distribution of the
13 * software without specific, written prior permission.
14 *
15 * IBM AND LEXMARK PROVIDE THIS SOFTWARE "AS IS", WITHOUT ANY WARRANTIES OF
16 * ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO ANY
17 * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE,
18 * AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. THE ENTIRE RISK AS TO THE
19 * QUALITY AND PERFORMANCE OF THE SOFTWARE, INCLUDING ANY DUTY TO SUPPORT
20 * OR MAINTAIN, BELONGS TO THE LICENSEE. SHOULD ANY PORTION OF THE
21 * SOFTWARE PROVE DEFECTIVE, THE LICENSEE (NOT IBM OR LEXMARK) ASSUMES THE
22 * ENTIRE COST OF ALL SERVICING, REPAIR AND CORRECTION. IN NO EVENT SHALL
23 * IBM OR LEXMARK BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL
24 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
25 * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
26 * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
27 * THIS SOFTWARE.
28 */
29 /* MALLOC CWEB V0004 LOTS */
30 /*
31 :h1.MALLOC - Fast Memory Allocation
32
33 This module is meant to provide portable C-style memory allocation
34 routines (malloc/free).
35
36 &author. Jeffrey B. Lotspiech (lotspiech@almaden.ibm.com)
37
38 */
39
40 #include "objects.h" /* get #define for abort() */
41
42 static combine();
43 static freeuncombinable();
44 static unhook();
45 static dumpchain();
46 /*
47 :h3.Define NULL
48
49 In the beginning, C compilers made no assumptions about NULL. It was
50 even theoretically possible that NULL would not be 0. ANSI has tied
51 this down a bit. The following definition seems to be the most
52 popular (in terms of reducing compiler complaints), however, if your
53 compiler is unhappy about it, you can redefine it on the command line:
54 */
55 #ifndef NULL
56 #define NULL 0
57 #endif
58 /*
59 Of course, NULL is important because xiMalloc() is defined to return
60 NULL when out of memory.
61
62 :h2.Data Structures Used to Manage Free Memory
63
64 :h3.The "freeblock" Structure
65
66 The list of available memory blocks is a doubly-linked list. Each
67 block begins with the following structure:
68 */
69
70 struct freeblock {
71 long size; /* number of 'longs' in block,
72 including this header */
73 struct freeblock *fore; /* forward in doubly-linked list */
74 struct freeblock *back; /* backward in doubly-linked list */
75 } ;
76 /*
77 In addition, each free block has a TRAILER that is simply the 'size'
78 repeated. Thus 'size' is found at the beginning of the block and at the
79 end of the block (size-1 longs away). 'size' includes both the header
80 and the trailer.
81
82 When a block is allocated, its 'size' is turned negative (both at the
83 beginning and at the end). Thus, checking whether two blocks may be
84 combined is very simple. We merely examine both neighboring blocks'
85 size to see if they are positive (and hence available for combination).
86
87 The memory address returned to the user is therefore one "long" below the
88 size, and one extra "long" is added to the end of the block (beyond what
89 the user requested) to store the trailing size.
90
91 :h3."firstfree" and "lastfree", the Anchors to the Free List
92
93 "firstfree" points to the first available free block; "lastfree" points
94 to the end of the chain of available blocks. These are linked together
95 by initialization code; see :hdref refid=addmem..
96 */
97
98 static struct freeblock firstfree = { 0L, NULL, NULL };
99 static struct freeblock lastfree = { 0L, NULL, NULL };
100
101 /*
102 :h3."firstcombined" and "uncombined", Keeping Track of Uncombined Blocks
103
104 This module is designed to make the combining of adjacent free memory
105 blocks be very fast. Nonetheless, combining blocks is naturally the
106 most expensive part of any memory system. In an X system,
107 it is worthwhile to defer the combination for a while, because
108 frequently we will end up asking for a block of exactly the same
109 size that we recently returned and we can save ourselves some work.
110
111 "MAXUNCOMBINED" is the maximum number of uncombined blocks that we will
112 allow at any time:
113 */
114
115 #define MAXUNCOMBINED 3
116
117 /*
118 "firstcombined" is a pointer into the free list. The uncombined blocks
119 are always at the front of the list. "firstcombined" points to the
120 first block that has been combined.
121 */
122 static struct freeblock *firstcombined = &lastfree;
123 static short uncombined = 0; /* current number of uncombined blocks */
124
125 /*
126 Uncombined blocks have a negative 'size'; in this they are like
127 allocated blocks.
128
129 We store a distinctive hex pattern in 'size' when we combine a block
130 to help us debug:
131 */
132 #define COMBINED 0xBADBAD
133
134 /*
135 :h3.DEBUGWORDS - Extra Memory Saved With Each Block for Debug
136
137 We add 'DEBUGWORDS' words to each allocated block to put interesting
138 debug information:
139 */
140 #ifndef DEBUGWORDS
141 #define DEBUGWORDS 0
142 #endif
143
144 /*
145 :h3.MINEXCESS - Amount of "Excess" We Would be Willing to Ignore
146
147 When we search the free list to find memory for a user request, we
148 frequently find an area that is bigger than what the user has asked for.
149 Normally we put the remaining words (the excess) back on the free list.
150 However, if the area is just slightly bigger than what the user needs,
151 it is counter-productive to do this, as the small amount recovered tends
152 to hurt by increasing memory fragmentation rather than help by providing
153 more available memory. "MINEXCESS" is the number of words that must be
154 recovered before we would bother to put the excess back on the free
155 list. If there is not enough excess, we just give the user more than he
156 asked for.
157 */
158
159 #define MINEXCESS (7 + DEBUGWORDS)
160
161 /*
162 :h3.Some Flags for Debug
163 */
164
165 long AvailableWords = 0; /* number of words available in memory */
166 char mallocdebug = 0; /* a flag that enables some chatty printf's */
167
168 /*
169 :h3.whocalledme() - Debug for Memory Leaks
170
171 This routine is 68000-specific; it copies the value of the application's
172 curOper variable (which is often a pointer to a character string), and
173 the first part of the stack at the time malloc was called into the
174 DEBUGWORDS area reserved with each block.
175 We use it to see who is malloc-ing memory without free-ing it.
176 */
177
178 #if DEBUGWORDS
179
whocalledme(addr,stack)180 static whocalledme(addr, stack)
181 long *addr; /* address of memory block */
182 long *stack; /* address of malloc's parameter on stack */
183 {
184 register long size; /* size of memory block */
185 register int i; /* loop index */
186 extern char *curOper; /* ptr to last operator (kept by appl.) */
187
188 stack--;
189 size = - *addr;
190
191 addr += size - 1 - DEBUGWORDS;
192 *addr++ = (long) curOper;
193 for (i=0; i < DEBUGWORDS-1; i++)
194 *addr++ = *stack++;
195 }
196 #else
197
198 #define whocalledme(addr, stack)
199
200 #endif
201 /*
202 :h2.xiFree() - User-Callable "Return Memory" Routine
203
204 The actual beginning of the block is one 'long' before the address we
205 gave to the user. The block begins and ends with '-size' in words.
206 */
207
xiFree(addr)208 void xiFree(addr)
209 register long *addr; /* user's memory to be returned */
210 {
211 register long size; /* amount of memory in this block */
212 register struct freeblock *p; /* identical to 'addr' */
213
214 if (addr == NULL) { /* common "mistake", so allow it (CHT) */
215 printf("\nxiFree(NULL)?\n");
216 return;
217 }
218
219 size = *--addr;
220 /*
221 Make sure this address looks OK; 'size' must be less than zero (meaning
222 the block is allocated) and should be repeated at the end of the block.
223 */
224 if (size >= 0)
225 abort("free: bad size");
226 if (addr[-1 - size] != size)
227 abort("free: mismatched size");
228 /*
229 Now make this a 'freeblock' structure and tack it on the FRONT of the
230 free list (where uncombined blocks go):
231 */
232 AvailableWords -= size; /* actually INCREASES AvailableWords */
233 p = (struct freeblock *) addr;
234 p->back = &firstfree;
235 (p->fore = firstfree.fore)->back = p;
236 firstfree.fore = p;
237 /*
238 If we have too many uncombined blocks, call combine() to combine one.
239 */
240 if (++uncombined > MAXUNCOMBINED) {
241 combine();
242 if (mallocdebug) {
243 printf("xiFree(%08x) with combine, ", addr);
244 dumpchain();
245 }
246 }
247 else {
248 if (mallocdebug) {
249 printf("xiFree(%08x), ", addr);
250 dumpchain();
251 }
252 }
253
254 return;
255 }
256
257 /*
258 :h3.combine() - Subroutine of xiFree() to Combine Blocks
259
260 This routine tries to combine the block just before 'firstcombined'.
261 In any event, that block will be moved to the end of the list (after
262 'firstcombined').
263 */
264
combine()265 static combine()
266 {
267 register struct freeblock *p; /* block we will try to combine */
268 register long *addr; /* identical to 'p' for 'long' access */
269 register long size; /* size of this block */
270 register long size2; /* size of potential combinee */
271
272 p = firstcombined->back;
273 if (p == &firstfree)
274 abort("why are we combining?");
275
276 addr = (long *) p;
277 size = - p->size;
278 if (--uncombined < 0)
279 abort("too many combine()s");
280
281 if (addr[-1] < 0 && addr[size] < 0) {
282 /*
283 We special case the situation where no combining can be done. Then, we
284 just mark the chain "combined" (i.e., positive size), move the
285 'firstcombined' pointer back in the chain, and return.
286 */
287 addr[0] = addr[size - 1] = size;
288 firstcombined = (struct freeblock *) addr;
289 return;
290 }
291 /*
292 Otherwise, we unhook this pointer from the chain:
293 */
294 unhook(p);
295 /*
296 First we attempt to combine this with the block immediately above:
297 */
298 size2 = addr[-1];
299 if (size2 > 0) { /* i.e., block above is free */
300 *addr = COMBINED; /* might help debug */
301 addr -= size2;
302 if (addr[0] != size2)
303 abort("bad block above");
304 unhook(addr);
305 size += size2;
306 }
307 /*
308 At this point 'addr' and 'size' may be the original block, or it may be
309 the newly combined block. Now we attempt to combine it with the block
310 below:
311 */
312 p = (struct freeblock *) (addr + size);
313 size2 = p->size;
314
315 if (size2 > 0) { /* i.e., block below is free */
316 p->size = COMBINED;
317 if (size2 != ((long *) p)[size2 - 1])
318 abort("bad block below");
319 unhook(p);
320 size += size2;
321 }
322 /*
323 Finally we take the newly combined block and put it on the end of the
324 chain by calling the "freeuncombinable" subroutine:
325 */
326 freeuncombinable(addr, size);
327 }
328
329 /*
330 :h3.freeuncombinable() - Free a Block That Need Not be Combined
331
332 This block is "uncombinable" either because we have already combined
333 it with its eligible neighbors, or perhaps because we know it has
334 no neighbors.
335 */
336
freeuncombinable(addr,size)337 static freeuncombinable(addr, size)
338 register long *addr; /* address of the block to be freed */
339 register long size; /* size of block in words */
340 {
341 register struct freeblock *p; /* a convenient synonym for 'addr' */
342
343 /*
344 Mark block allocated and combined by setting its 'size' positive:
345 */
346 addr[size - 1] = addr[0] = size;
347 /*
348 Now tack the block on the end of the doubly-linked free list:
349 */
350 p = (struct freeblock *) addr;
351 p->fore = &lastfree;
352 (p->back = lastfree.back)->fore = p;
353 lastfree.back = p;
354 /*
355 If we have previously had no combined blocks, we must update
356 'firstcombined' to point to this block:
357 */
358 if (firstcombined->fore == NULL)
359 firstcombined = p;
360 }
361
362 /*
363 :h3.unhook() - Unhook a Block from the Doubly-linked List
364
365 The only tricky thing here is to make sure that 'firstcombined' is
366 updated if this block happened to be the old 'firstcombined'. (We
367 would never be unhooking 'firstfree' or 'lastfree', so we do not
368 have to worry about the end cases.)
369 */
370
unhook(p)371 static unhook(p)
372 register struct freeblock *p; /* block to unhook */
373 {
374 p->back->fore = p->fore;
375 p->fore->back = p->back;
376
377 if (firstcombined == p)
378 firstcombined = p->fore;
379 }
380 /*
381 :h2.xiMalloc() - Main User Entry Point for Getting Memory
382
383 We have two slightly different versions of xiMalloc(). In the case
384 where we have TYPE1IMAGER and a font cache, we are prepared, when nominally
385 out of memory, to loop calling TYPE1IMAGER's GimeSpace() to release font
386 cache.
387 */
388
389 /* The following code put in by MDC on 11/10/90 */
390
391 #ifdef TYPE1IMAGER
392
393 static char *malloc_local();
394
xiMalloc(size)395 char *xiMalloc(size)
396 register unsigned size;
397 {
398 char *memaddr;
399
400 while ( (memaddr = malloc_local(size)) == NULL ) {
401 /* Ask TYPE1IMAGER to give us some of its cache back */
402 if ( I_GimeSpace() == 0 ) break; /* We are really, really, out of memory */
403 }
404
405 return(memaddr);
406 }
407 #endif
408
409 /*
410 Now begins the real workhorse xiMalloc() (called 'malloc_local' if
411 we are taking advantage of TYPE1IMAGER). Its argument is an unsigned;
412 at least that lets users with 16-bit integers get a 64K chunk of
413 memory, and it is also compatible with the definition of a "size_t"
414 in most systems.
415 */
416 #ifdef TYPE1IMAGER
malloc_local(Size)417 static char *malloc_local(Size)
418 #else
419 char *xiMalloc(Size)
420 #endif
421 unsigned Size; /* number of bytes the user requested */
422 {
423 register long size = (long)Size; /* a working register for size */
424 register struct freeblock *p; /* tentative block to be returned */
425 register long excess; /* words in excess of user request */
426 register long *area; /* a convenient synonym for 'p' */
427
428 /*
429 First, we increase 'size' to allow for the two size fields we will
430 save with the block, plus any information for debug purposes.
431 Then we ensure that the block will be large enough to hold our
432 'freeblock' information. Finally we convert it to be in words
433 (longs), not bytes, increased to span an integral number of double
434 words, so that all memory blocks dispensed with be properly aligned.
435 */
436 size += 2*sizeof(long) + DEBUGWORDS*sizeof(long);
437 if (size < sizeof(struct freeblock) + sizeof(long))
438 size = sizeof(struct freeblock) + sizeof(long);
439 size = ((unsigned) (size + sizeof(double) - 1) / sizeof(double)) * (sizeof(double)/sizeof(long));
440
441 /*
442 For speed, we will try first to give the user back a very recently
443 returned block--one that is on the front of the chain before
444 'firstcombined'. These blocks still have negative sizes, and need
445 only to be "unhook"ed:
446 */
447 size = -size;
448 for (p=firstfree.fore; p != firstcombined; p=p->fore) {
449 if (p->size == size) {
450 unhook(p);
451 uncombined--;
452 if (mallocdebug) {
453 printf("fast xiMalloc(%d) = %08x, ", size, p);
454 dumpchain();
455 }
456 AvailableWords += size; /* decreases AvailableWords */
457 whocalledme(p, &Size);
458 return((char *)&p->fore);
459 }
460 }
461 /*
462 Well, if we get here, there are no uncombined blocks matching the user's
463 request. So, we search the rest of the chain for a block that is big
464 enough. ('size' becomes positive again):
465 */
466 size = -size;
467 for (;; p = p->fore) {
468 /*
469 If we hit the end of the chain (p->size == 0), we are probably out of
470 memory. However, we should first try to combine any memory that has
471 not yet been combined before we give that pessimistic answer. If
472 we succeed in combining, we can call ourselves recursively to try to
473 allocate the requested amount:
474 */
475 if (p->size == 0) {
476 if (uncombined <= 0)
477 return(NULL);
478 while (firstfree.fore != firstcombined)
479 combine();
480 return(xiMalloc(sizeof(long) * (size - 2 - DEBUGWORDS)));
481 }
482 /*
483 Otherwise, we keep searching until we find a big enough block:
484 */
485 if (p->size >= size)
486 break;
487 }
488 /*
489 At this point, 'p' contains a block at least as big as what the user
490 requested, so we take it off the free chain. If it is excessively big,
491 we return the excess to the free chain:
492 */
493 unhook(p);
494 excess = p->size - size;
495 area = (long *) p;
496
497 if (excess > MINEXCESS)
498 freeuncombinable(area + size, excess);
499 else
500 size = p->size;
501
502 AvailableWords -= size;
503 /*
504 Mark first and last word of block with the negative of the size, to
505 flag that this block is allocated:
506 */
507 area[size - 1] = area[0] = - size;
508
509 if (mallocdebug) {
510 printf("slow xiMalloc(%d) @ %08x, ", size, area);
511 dumpchain();
512 }
513 whocalledme(area, &Size);
514 /*
515 The address we return to the user is one 'long' BELOW the address of
516 the block. This protects our 'size' field, so we can tell the size
517 of the block when he returns it to us with xiFree(). Also, he better not
518 touch the 'size' field at the end of the block either. (That would be
519 nasty of him, as he would be touching memory outside of the bytes he
520 requested).
521 */
522 return((char *) (area + 1));
523 }
524
525 /*
526 :h2 id=addmem.addmemory() - Initialize Free Memory
527
528 This routine should be called at initialization to initialize the
529 free chain. There is no standard way to do this in C.
530 We want the memory dispensed by malloc to be aligned on a double word
531 boundary (because some machines either require alignment, or are
532 more efficient if accesses are aligned). Since the total size of
533 any block created by malloc is an integral number of double words,
534 all we have to do to ensure alignment is to adjust each large block
535 added to the free chain to start on an odd long-word boundary.
536 (Malloc's size field will occupy the odd long and the user's memory
537 will then begin on an even boundary.) Since we fill in additional
538 size fields at the beginning and end of each of the large freeblocks,
539 we need only adjust the address passed to addmemory to a double word
540 boundary.
541 */
542
543 #define MAXAREAS 10 /* there can be this many calls to addmemory() */
544
545 static long *freearea[MAXAREAS] = { NULL }; /* so we can report later */
546
addmemory(addr,size)547 void addmemory(addr, size)
548 register long *addr; /* beginning of free area */
549 register long size; /* number of bytes of free area */
550 {
551 register int i; /* loop index variable */
552 register long *aaddr; /* aligned beginning of free area */
553
554 #if DEBUGWORDS
555 printf("malloc has DEBUGWORDS=%d\n", DEBUGWORDS);
556 #endif
557 /*
558 First link together firstfree and lastfree if necessary:
559 */
560 if (firstfree.fore == NULL) {
561 firstfree.fore = &lastfree;
562 lastfree.back = &firstfree;
563 }
564 /*
565 We'll record where the area was that was given to us for later reports:
566 */
567 for (i=0; i < MAXAREAS; i++)
568 if (freearea[i] == NULL) break;
569 if (i >= MAXAREAS)
570 abort("too many addmemory()s");
571 aaddr = (long *) ( ((long) addr + sizeof(double) - 1) & - (long)sizeof(double) );
572 size -= (char *) aaddr - (char *) addr;
573 freearea[i] = aaddr;
574 /*
575 Convert 'size' to number of longs, and store '-size' guards at the
576 beginning and end of this area so we will not accidentally recombine the
577 first or last block:
578 */
579 size /= sizeof(long);
580
581 AvailableWords += size - 2;
582
583 aaddr[size - 1] = aaddr[0] = -size;
584 /*
585 Finally, call 'freeuncombinable' to put the remaining memory on the
586 free list:
587 */
588 freeuncombinable(aaddr + 1, size - 2);
589 }
590
591 /*
592 :h3.delmemory() - Delete Memory Pool
593 */
delmemory()594 void delmemory()
595 {
596 register int i;
597
598 AvailableWords = 0;
599 firstfree.fore = &lastfree;
600 lastfree.back = &firstfree;
601 firstcombined = &lastfree;
602 uncombined = 0;
603 for (i=0; i<MAXAREAS; i++)
604 freearea[i] = NULL;
605 }
606
607 /*
608 :h2.Debug Routines
609
610 :h3.dumpchain() - Print the Chain of Free Blocks
611 */
612
dumpchain()613 static dumpchain()
614 {
615 register struct freeblock *p; /* current free block */
616 register long size; /* size of block */
617 register struct freeblock *back; /* block before 'p' */
618 register int i; /* temp variable for counting */
619
620 printf("DUMPING FAST FREE LIST:\n");
621 back = &firstfree;
622 for (p = firstfree.fore, i=uncombined; p != firstcombined;
623 p = p->fore) {
624 if (--i < 0)
625 abort("too many uncombined areas");
626 size = p->size;
627 printf(". . . area @ %08x, size = %ld\n", p, -size);
628 if (size >= 0 || size != ((int *) p)[-1 - size])
629 abort("dumpchain: bad size");
630 if (p->back != back)
631 abort("dumpchain: bad back");
632 back = p;
633 }
634 printf("DUMPING COMBINED FREE LIST:\n");
635 for (; p != &lastfree; p = p->fore) {
636 size = p->size;
637 printf(". . . area @ %08x, size = %d\n", p, size);
638 if (size <= 0 || size != ((int *) p)[size - 1])
639 abort("dumpchain: bad size");
640 if (p->back != back)
641 abort("dumpchain: bad back");
642 back = p;
643 }
644 if (back != lastfree.back)
645 abort("dumpchain: bad lastfree");
646 }
647
648 /*
649 :h3.reportarea() - Display a Contiguous Set of Memory Blocks
650 */
651
reportarea(area)652 static reportarea(area)
653 register long *area; /* start of blocks (from addmemory) */
654 {
655 register long size; /* size of current block */
656 register long wholesize; /* size of original area */
657 register struct freeblock *p; /* pointer to block */
658
659 if (area == NULL)
660 return;
661 wholesize = - *area++;
662 wholesize -= 2;
663
664 while (wholesize > 0) {
665 size = *area;
666 if (size < 0) {
667 register int i,j;
668
669 size = -size;
670 printf("Allocated %5d bytes at %08x, first words=%08x %08x\n",
671 size * sizeof(long), area + 1, area[1], area[2]);
672 #if DEBUGWORDS
673 printf(" ...Last operator: %s\n",
674 (char *)area[size-DEBUGWORDS-1]);
675 #endif
676 for (i = size - DEBUGWORDS; i < size - 2; i += 8) {
677 printf(" ...");
678 for (j=0; j<8; j++)
679 printf(" %08x", area[i+j]);
680 printf("\n");
681 }
682
683 }
684 else {
685 printf("Free %d bytes at %x\n", size * sizeof(long),
686 area);
687 if (size == 0)
688 abort("zero sized memory block");
689
690 for (p = firstfree.fore; p != NULL; p = p->fore)
691 if ((long *) p == area) break;
692 if ((long *) p != area)
693 abort("not found on forward chain");
694
695 for (p = lastfree.back; p != NULL; p = p->back)
696 if ((long *) p == area) break;
697 if ((long *) p != area)
698 abort("not found on backward chain");
699 }
700 if (area[0] != area[size - 1])
701 abort("unmatched check sizes");
702 area += size;
703 wholesize -= size;
704 }
705 }
706
707 /*
708 :h3.MemReport() - Display All of Memory
709 */
710
MemReport()711 MemReport()
712 {
713 register int i;
714
715 dumpchain();
716
717 for (i=0; i<MAXAREAS; i++)
718 reportarea(freearea[i]);
719 }
720
721 /*
722 :h3.MemBytesAvail - Display Number of Bytes Now Available
723 */
724
MemBytesAvail()725 MemBytesAvail()
726 {
727 printf("There are now %d bytes available\n", AvailableWords *
728 sizeof(long) );
729 }
730