malloc.c revision 110803
134192Sjdp/*- 234192Sjdp * Copyright (c) 1983 Regents of the University of California. 334192Sjdp * All rights reserved. 434192Sjdp * 534192Sjdp * Redistribution and use in source and binary forms, with or without 634192Sjdp * modification, are permitted provided that the following conditions 734192Sjdp * are met: 834192Sjdp * 1. Redistributions of source code must retain the above copyright 934192Sjdp * notice, this list of conditions and the following disclaimer. 1034192Sjdp * 2. Redistributions in binary form must reproduce the above copyright 1134192Sjdp * notice, this list of conditions and the following disclaimer in the 1234192Sjdp * documentation and/or other materials provided with the distribution. 1334192Sjdp * 3. All advertising materials mentioning features or use of this software 1434192Sjdp * must display the following acknowledgement: 1534192Sjdp * This product includes software developed by the University of 1634192Sjdp * California, Berkeley and its contributors. 1734192Sjdp * 4. Neither the name of the University nor the names of its contributors 1834192Sjdp * may be used to endorse or promote products derived from this software 1934192Sjdp * without specific prior written permission. 2034192Sjdp * 2134192Sjdp * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 2234192Sjdp * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 2334192Sjdp * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 2434192Sjdp * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 2534192Sjdp * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 2634192Sjdp * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 2734192Sjdp * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 2834192Sjdp * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 2934192Sjdp * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 3034192Sjdp * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 3134192Sjdp * SUCH DAMAGE. 3234192Sjdp */ 3334192Sjdp 3434192Sjdp#if defined(LIBC_SCCS) && !defined(lint) 3534192Sjdp/*static char *sccsid = "from: @(#)malloc.c 5.11 (Berkeley) 2/23/91";*/ 3650476Speterstatic char *rcsid = "$FreeBSD: head/libexec/rtld-elf/malloc.c 110803 2003-02-13 17:35:00Z kan $"; 3734192Sjdp#endif /* LIBC_SCCS and not lint */ 3834192Sjdp 3934192Sjdp/* 4034192Sjdp * malloc.c (Caltech) 2/21/82 4134192Sjdp * Chris Kingsley, kingsley@cit-20. 4234192Sjdp * 4334192Sjdp * This is a very fast storage allocator. It allocates blocks of a small 4434192Sjdp * number of different sizes, and keeps free lists of each size. Blocks that 4534192Sjdp * don't exactly fit are passed up to the next larger size. In this 4634192Sjdp * implementation, the available sizes are 2^n-4 (or 2^n-10) bytes long. 4734192Sjdp * This is designed for use in a virtual memory environment. 4834192Sjdp */ 4934192Sjdp 5034192Sjdp#include <sys/types.h> 5134192Sjdp#include <err.h> 5269793Sobrien#include <paths.h> 53110803Skan#include <stdarg.h> 54110803Skan#include <stdio.h> 5534192Sjdp#include <stdlib.h> 5634192Sjdp#include <string.h> 57102235Simp#include <stddef.h> 5834192Sjdp#include <unistd.h> 5934192Sjdp#include <sys/param.h> 6034192Sjdp#include <sys/mman.h> 6134192Sjdp#ifndef BSD 6234192Sjdp#define MAP_COPY MAP_PRIVATE 6334192Sjdp#define MAP_FILE 0 6434192Sjdp#define MAP_ANON 0 6534192Sjdp#endif 6634192Sjdp 6734192Sjdp#ifndef BSD /* Need do better than this */ 6834192Sjdp#define NEED_DEV_ZERO 1 6934192Sjdp#endif 7034192Sjdp 7134192Sjdpstatic void morecore(); 7234192Sjdpstatic int findbucket(); 7334192Sjdp 7434192Sjdp/* 7534192Sjdp * Pre-allocate mmap'ed pages 7634192Sjdp */ 7734192Sjdp#define NPOOLPAGES (32*1024/pagesz) 7834192Sjdpstatic caddr_t pagepool_start, pagepool_end; 7934192Sjdpstatic int morepages(); 8034192Sjdp 8134192Sjdp/* 8234192Sjdp * The overhead on a block is at least 4 bytes. When free, this space 8334192Sjdp * contains a pointer to the next free block, and the bottom two bits must 8434192Sjdp * be zero. When in use, the first byte is set to MAGIC, and the second 8534192Sjdp * byte is the size index. The remaining bytes are for alignment. 8634192Sjdp * If range checking is enabled then a second word holds the size of the 8734192Sjdp * requested block, less 1, rounded up to a multiple of sizeof(RMAGIC). 8834192Sjdp * The order of elements is critical: ov_magic must overlay the low order 8934192Sjdp * bits of ov_next, and ov_magic can not be a valid ov_next bit pattern. 9034192Sjdp */ 9134192Sjdpunion overhead { 9234192Sjdp union overhead *ov_next; /* when free */ 9334192Sjdp struct { 9434192Sjdp u_char ovu_magic; /* magic number */ 9534192Sjdp u_char ovu_index; /* bucket # */ 9634192Sjdp#ifdef RCHECK 9734192Sjdp u_short ovu_rmagic; /* range magic number */ 9834192Sjdp u_int ovu_size; /* actual block size */ 9934192Sjdp#endif 10034192Sjdp } ovu; 10134192Sjdp#define ov_magic ovu.ovu_magic 10234192Sjdp#define ov_index ovu.ovu_index 10334192Sjdp#define ov_rmagic ovu.ovu_rmagic 10434192Sjdp#define ov_size ovu.ovu_size 10534192Sjdp}; 10634192Sjdp 10734192Sjdp#define MAGIC 0xef /* magic # on accounting info */ 10834192Sjdp#define RMAGIC 0x5555 /* magic # on range info */ 10934192Sjdp 11034192Sjdp#ifdef RCHECK 11134192Sjdp#define RSLOP sizeof (u_short) 11234192Sjdp#else 11334192Sjdp#define RSLOP 0 11434192Sjdp#endif 11534192Sjdp 11634192Sjdp/* 11734192Sjdp * nextf[i] is the pointer to the next free block of size 2^(i+3). The 11834192Sjdp * smallest allocatable block is 8 bytes. The overhead information 11934192Sjdp * precedes the data area returned to the user. 12034192Sjdp */ 12134192Sjdp#define NBUCKETS 30 12234192Sjdpstatic union overhead *nextf[NBUCKETS]; 12334192Sjdp 12434192Sjdpstatic int pagesz; /* page size */ 12534192Sjdpstatic int pagebucket; /* page size bucket */ 12634192Sjdp 12734192Sjdp#ifdef MSTATS 12834192Sjdp/* 12934192Sjdp * nmalloc[i] is the difference between the number of mallocs and frees 13034192Sjdp * for a given block size. 13134192Sjdp */ 13234192Sjdpstatic u_int nmalloc[NBUCKETS]; 13334192Sjdp#include <stdio.h> 13434192Sjdp#endif 13534192Sjdp 13634192Sjdp#if defined(MALLOC_DEBUG) || defined(RCHECK) 13734192Sjdp#define ASSERT(p) if (!(p)) botch("p") 13834192Sjdp#include <stdio.h> 13934192Sjdpstatic void 14034192Sjdpbotch(s) 14134192Sjdp char *s; 14234192Sjdp{ 14334192Sjdp fprintf(stderr, "\r\nassertion botched: %s\r\n", s); 14434192Sjdp (void) fflush(stderr); /* just in case user buffered it */ 14534192Sjdp abort(); 14634192Sjdp} 14734192Sjdp#else 14834192Sjdp#define ASSERT(p) 14934192Sjdp#endif 15034192Sjdp 15134192Sjdp/* Debugging stuff */ 152110801Skanstatic void xprintf(const char *, ...); 15334192Sjdp#define TRACE() xprintf("TRACE %s:%d\n", __FILE__, __LINE__) 15434192Sjdp 15534192Sjdpvoid * 15634192Sjdpmalloc(nbytes) 15734192Sjdp size_t nbytes; 15834192Sjdp{ 15934192Sjdp register union overhead *op; 16038816Sdfr register int bucket; 16138816Sdfr register long n; 16234192Sjdp register unsigned amt; 16334192Sjdp 16434192Sjdp /* 16534192Sjdp * First time malloc is called, setup page size and 16634192Sjdp * align break pointer so all data will be page aligned. 16734192Sjdp */ 16834192Sjdp if (pagesz == 0) { 16934192Sjdp pagesz = n = getpagesize(); 17034192Sjdp if (morepages(NPOOLPAGES) == 0) 17134192Sjdp return NULL; 17234192Sjdp op = (union overhead *)(pagepool_start); 17338816Sdfr n = n - sizeof (*op) - ((long)op & (n - 1)); 17434192Sjdp if (n < 0) 17534192Sjdp n += pagesz; 17634192Sjdp if (n) { 17734192Sjdp pagepool_start += n; 17834192Sjdp } 17934192Sjdp bucket = 0; 18034192Sjdp amt = 8; 18134192Sjdp while (pagesz > amt) { 18234192Sjdp amt <<= 1; 18334192Sjdp bucket++; 18434192Sjdp } 18534192Sjdp pagebucket = bucket; 18634192Sjdp } 18734192Sjdp /* 18834192Sjdp * Convert amount of memory requested into closest block size 18934192Sjdp * stored in hash buckets which satisfies request. 19034192Sjdp * Account for space used per block for accounting. 19134192Sjdp */ 19234192Sjdp if (nbytes <= (n = pagesz - sizeof (*op) - RSLOP)) { 19334192Sjdp#ifndef RCHECK 19434192Sjdp amt = 8; /* size of first bucket */ 19534192Sjdp bucket = 0; 19634192Sjdp#else 19734192Sjdp amt = 16; /* size of first bucket */ 19834192Sjdp bucket = 1; 19934192Sjdp#endif 20034192Sjdp n = -(sizeof (*op) + RSLOP); 20134192Sjdp } else { 20234192Sjdp amt = pagesz; 20334192Sjdp bucket = pagebucket; 20434192Sjdp } 20534192Sjdp while (nbytes > amt + n) { 20634192Sjdp amt <<= 1; 20734192Sjdp if (amt == 0) 20834192Sjdp return (NULL); 20934192Sjdp bucket++; 21034192Sjdp } 21134192Sjdp /* 21234192Sjdp * If nothing in hash bucket right now, 21334192Sjdp * request more memory from the system. 21434192Sjdp */ 21534192Sjdp if ((op = nextf[bucket]) == NULL) { 21634192Sjdp morecore(bucket); 21734192Sjdp if ((op = nextf[bucket]) == NULL) 21834192Sjdp return (NULL); 21934192Sjdp } 22034192Sjdp /* remove from linked list */ 22134192Sjdp nextf[bucket] = op->ov_next; 22234192Sjdp op->ov_magic = MAGIC; 22334192Sjdp op->ov_index = bucket; 22434192Sjdp#ifdef MSTATS 22534192Sjdp nmalloc[bucket]++; 22634192Sjdp#endif 22734192Sjdp#ifdef RCHECK 22834192Sjdp /* 22934192Sjdp * Record allocated size of block and 23034192Sjdp * bound space with magic numbers. 23134192Sjdp */ 23234192Sjdp op->ov_size = (nbytes + RSLOP - 1) & ~(RSLOP - 1); 23334192Sjdp op->ov_rmagic = RMAGIC; 23434192Sjdp *(u_short *)((caddr_t)(op + 1) + op->ov_size) = RMAGIC; 23534192Sjdp#endif 23634192Sjdp return ((char *)(op + 1)); 23734192Sjdp} 23834192Sjdp 23934192Sjdp/* 24034192Sjdp * Allocate more memory to the indicated bucket. 24134192Sjdp */ 24234192Sjdpstatic void 24334192Sjdpmorecore(bucket) 24434192Sjdp int bucket; 24534192Sjdp{ 24634192Sjdp register union overhead *op; 24734192Sjdp register int sz; /* size of desired block */ 24834192Sjdp int amt; /* amount to allocate */ 24934192Sjdp int nblks; /* how many blocks we get */ 25034192Sjdp 25134192Sjdp /* 25234192Sjdp * sbrk_size <= 0 only for big, FLUFFY, requests (about 25334192Sjdp * 2^30 bytes on a VAX, I think) or for a negative arg. 25434192Sjdp */ 25534192Sjdp sz = 1 << (bucket + 3); 25634192Sjdp#ifdef MALLOC_DEBUG 25734192Sjdp ASSERT(sz > 0); 25834192Sjdp#else 25934192Sjdp if (sz <= 0) 26034192Sjdp return; 26134192Sjdp#endif 26234192Sjdp if (sz < pagesz) { 26334192Sjdp amt = pagesz; 26434192Sjdp nblks = amt / sz; 26534192Sjdp } else { 26634192Sjdp amt = sz + pagesz; 26734192Sjdp nblks = 1; 26834192Sjdp } 26934192Sjdp if (amt > pagepool_end - pagepool_start) 27034192Sjdp if (morepages(amt/pagesz + NPOOLPAGES) == 0) 27134192Sjdp return; 27234192Sjdp op = (union overhead *)pagepool_start; 27334192Sjdp pagepool_start += amt; 27434192Sjdp 27534192Sjdp /* 27634192Sjdp * Add new memory allocated to that on 27734192Sjdp * free list for this hash bucket. 27834192Sjdp */ 27934192Sjdp nextf[bucket] = op; 28034192Sjdp while (--nblks > 0) { 28134192Sjdp op->ov_next = (union overhead *)((caddr_t)op + sz); 28234192Sjdp op = (union overhead *)((caddr_t)op + sz); 28334192Sjdp } 28434192Sjdp} 28534192Sjdp 28634192Sjdpvoid 28734192Sjdpfree(cp) 28834192Sjdp void *cp; 28934192Sjdp{ 29034192Sjdp register int size; 29134192Sjdp register union overhead *op; 29234192Sjdp 29334192Sjdp if (cp == NULL) 29434192Sjdp return; 29534192Sjdp op = (union overhead *)((caddr_t)cp - sizeof (union overhead)); 29634192Sjdp#ifdef MALLOC_DEBUG 29734192Sjdp ASSERT(op->ov_magic == MAGIC); /* make sure it was in use */ 29834192Sjdp#else 29934192Sjdp if (op->ov_magic != MAGIC) 30034192Sjdp return; /* sanity */ 30134192Sjdp#endif 30234192Sjdp#ifdef RCHECK 30334192Sjdp ASSERT(op->ov_rmagic == RMAGIC); 30434192Sjdp ASSERT(*(u_short *)((caddr_t)(op + 1) + op->ov_size) == RMAGIC); 30534192Sjdp#endif 30634192Sjdp size = op->ov_index; 30734192Sjdp ASSERT(size < NBUCKETS); 30834192Sjdp op->ov_next = nextf[size]; /* also clobbers ov_magic */ 30934192Sjdp nextf[size] = op; 31034192Sjdp#ifdef MSTATS 31134192Sjdp nmalloc[size]--; 31234192Sjdp#endif 31334192Sjdp} 31434192Sjdp 31534192Sjdp/* 31634192Sjdp * When a program attempts "storage compaction" as mentioned in the 31734192Sjdp * old malloc man page, it realloc's an already freed block. Usually 31834192Sjdp * this is the last block it freed; occasionally it might be farther 31934192Sjdp * back. We have to search all the free lists for the block in order 32034192Sjdp * to determine its bucket: 1st we make one pass thru the lists 32134192Sjdp * checking only the first block in each; if that fails we search 32234192Sjdp * ``realloc_srchlen'' blocks in each list for a match (the variable 32334192Sjdp * is extern so the caller can modify it). If that fails we just copy 32434192Sjdp * however many bytes was given to realloc() and hope it's not huge. 32534192Sjdp */ 32634192Sjdpint realloc_srchlen = 4; /* 4 should be plenty, -1 =>'s whole list */ 32734192Sjdp 32834192Sjdpvoid * 32934192Sjdprealloc(cp, nbytes) 33034192Sjdp void *cp; 33134192Sjdp size_t nbytes; 33234192Sjdp{ 33334192Sjdp register u_int onb; 33434192Sjdp register int i; 33534192Sjdp union overhead *op; 33634192Sjdp char *res; 33734192Sjdp int was_alloced = 0; 33834192Sjdp 33934192Sjdp if (cp == NULL) 34034192Sjdp return (malloc(nbytes)); 34134192Sjdp op = (union overhead *)((caddr_t)cp - sizeof (union overhead)); 34234192Sjdp if (op->ov_magic == MAGIC) { 34334192Sjdp was_alloced++; 34434192Sjdp i = op->ov_index; 34534192Sjdp } else { 34634192Sjdp /* 34734192Sjdp * Already free, doing "compaction". 34834192Sjdp * 34934192Sjdp * Search for the old block of memory on the 35034192Sjdp * free list. First, check the most common 35134192Sjdp * case (last element free'd), then (this failing) 35234192Sjdp * the last ``realloc_srchlen'' items free'd. 35334192Sjdp * If all lookups fail, then assume the size of 35434192Sjdp * the memory block being realloc'd is the 35534192Sjdp * largest possible (so that all "nbytes" of new 35634192Sjdp * memory are copied into). Note that this could cause 35734192Sjdp * a memory fault if the old area was tiny, and the moon 35834192Sjdp * is gibbous. However, that is very unlikely. 35934192Sjdp */ 36034192Sjdp if ((i = findbucket(op, 1)) < 0 && 36134192Sjdp (i = findbucket(op, realloc_srchlen)) < 0) 36234192Sjdp i = NBUCKETS; 36334192Sjdp } 36434192Sjdp onb = 1 << (i + 3); 36534192Sjdp if (onb < pagesz) 36634192Sjdp onb -= sizeof (*op) + RSLOP; 36734192Sjdp else 36834192Sjdp onb += pagesz - sizeof (*op) - RSLOP; 36934192Sjdp /* avoid the copy if same size block */ 37034192Sjdp if (was_alloced) { 37134192Sjdp if (i) { 37234192Sjdp i = 1 << (i + 2); 37334192Sjdp if (i < pagesz) 37434192Sjdp i -= sizeof (*op) + RSLOP; 37534192Sjdp else 37634192Sjdp i += pagesz - sizeof (*op) - RSLOP; 37734192Sjdp } 37834192Sjdp if (nbytes <= onb && nbytes > i) { 37934192Sjdp#ifdef RCHECK 38034192Sjdp op->ov_size = (nbytes + RSLOP - 1) & ~(RSLOP - 1); 38134192Sjdp *(u_short *)((caddr_t)(op + 1) + op->ov_size) = RMAGIC; 38234192Sjdp#endif 38334192Sjdp return(cp); 38434192Sjdp } else 38534192Sjdp free(cp); 38634192Sjdp } 38734192Sjdp if ((res = malloc(nbytes)) == NULL) 38834192Sjdp return (NULL); 38934192Sjdp if (cp != res) /* common optimization if "compacting" */ 39034192Sjdp bcopy(cp, res, (nbytes < onb) ? nbytes : onb); 39134192Sjdp return (res); 39234192Sjdp} 39334192Sjdp 39434192Sjdp/* 39534192Sjdp * Search ``srchlen'' elements of each free list for a block whose 39634192Sjdp * header starts at ``freep''. If srchlen is -1 search the whole list. 39734192Sjdp * Return bucket number, or -1 if not found. 39834192Sjdp */ 39934192Sjdpstatic int 40034192Sjdpfindbucket(freep, srchlen) 40134192Sjdp union overhead *freep; 40234192Sjdp int srchlen; 40334192Sjdp{ 40434192Sjdp register union overhead *p; 40534192Sjdp register int i, j; 40634192Sjdp 40734192Sjdp for (i = 0; i < NBUCKETS; i++) { 40834192Sjdp j = 0; 40934192Sjdp for (p = nextf[i]; p && j != srchlen; p = p->ov_next) { 41034192Sjdp if (p == freep) 41134192Sjdp return (i); 41234192Sjdp j++; 41334192Sjdp } 41434192Sjdp } 41534192Sjdp return (-1); 41634192Sjdp} 41734192Sjdp 41834192Sjdp#ifdef MSTATS 41934192Sjdp/* 42034192Sjdp * mstats - print out statistics about malloc 42134192Sjdp * 42234192Sjdp * Prints two lines of numbers, one showing the length of the free list 42334192Sjdp * for each size category, the second showing the number of mallocs - 42434192Sjdp * frees for each size category. 42534192Sjdp */ 42634192Sjdpmstats(s) 42734192Sjdp char *s; 42834192Sjdp{ 42934192Sjdp register int i, j; 43034192Sjdp register union overhead *p; 43134192Sjdp int totfree = 0, 43234192Sjdp totused = 0; 43334192Sjdp 43434192Sjdp fprintf(stderr, "Memory allocation statistics %s\nfree:\t", s); 43534192Sjdp for (i = 0; i < NBUCKETS; i++) { 43634192Sjdp for (j = 0, p = nextf[i]; p; p = p->ov_next, j++) 43734192Sjdp ; 43834192Sjdp fprintf(stderr, " %d", j); 43934192Sjdp totfree += j * (1 << (i + 3)); 44034192Sjdp } 44134192Sjdp fprintf(stderr, "\nused:\t"); 44234192Sjdp for (i = 0; i < NBUCKETS; i++) { 44334192Sjdp fprintf(stderr, " %d", nmalloc[i]); 44434192Sjdp totused += nmalloc[i] * (1 << (i + 3)); 44534192Sjdp } 44634192Sjdp fprintf(stderr, "\n\tTotal in use: %d, total free: %d\n", 44734192Sjdp totused, totfree); 44834192Sjdp} 44934192Sjdp#endif 45034192Sjdp 45134192Sjdp 45234192Sjdpstatic int 45334192Sjdpmorepages(n) 45434192Sjdpint n; 45534192Sjdp{ 45634192Sjdp int fd = -1; 45734192Sjdp int offset; 45834192Sjdp 45934192Sjdp#ifdef NEED_DEV_ZERO 46069793Sobrien fd = open(_PATH_DEVZERO, O_RDWR, 0); 46134192Sjdp if (fd == -1) 46269793Sobrien perror(_PATH_DEVZERO); 46334192Sjdp#endif 46434192Sjdp 46534192Sjdp if (pagepool_end - pagepool_start > pagesz) { 46634192Sjdp caddr_t addr = (caddr_t) 46738816Sdfr (((long)pagepool_start + pagesz - 1) & ~(pagesz - 1)); 46834192Sjdp if (munmap(addr, pagepool_end - addr) != 0) 46934192Sjdp warn("morepages: munmap %p", addr); 47034192Sjdp } 47134192Sjdp 47238816Sdfr offset = (long)pagepool_start - ((long)pagepool_start & ~(pagesz - 1)); 47334192Sjdp 47434192Sjdp if ((pagepool_start = mmap(0, n * pagesz, 47534192Sjdp PROT_READ|PROT_WRITE, 47634192Sjdp MAP_ANON|MAP_COPY, fd, 0)) == (caddr_t)-1) { 47734192Sjdp xprintf("Cannot map anonymous memory"); 47834192Sjdp return 0; 47934192Sjdp } 48034192Sjdp pagepool_end = pagepool_start + n * pagesz; 48134192Sjdp pagepool_start += offset; 48234192Sjdp 48334192Sjdp#ifdef NEED_DEV_ZERO 48434192Sjdp close(fd); 48534192Sjdp#endif 48634192Sjdp return n; 48734192Sjdp} 488110801Skan 489110801Skan/* 490110801Skan * Non-mallocing printf, for use by malloc itself. 491110801Skan */ 492110801Skanstatic void 493110801Skanxprintf(const char *fmt, ...) 494110801Skan{ 495110801Skan char buf[256]; 496110801Skan va_list ap; 497110801Skan 498110801Skan va_start(ap, fmt); 499110801Skan vsprintf(buf, fmt, ap); 500110801Skan (void)write(STDOUT_FILENO, buf, strlen(buf)); 501110801Skan va_end(ap); 502110801Skan} 503