summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorH. Peter Anvin <hpa@zytor.com>2008-02-21 17:59:56 -0800
committerH. Peter Anvin <hpa@zytor.com>2008-02-21 17:59:56 -0800
commitce6265e0c4577aa7871e337cec549b0d844706ce (patch)
treedae820e5af1b412c013de6ac4ffa8ae8471fbe98
parent427ab5add9a1002b8e8cefe3a85f4497eb977e73 (diff)
downloadsyslinux-ce6265e0c4577aa7871e337cec549b0d844706ce.tar.gz
realloc(): better implementation allowing in-place growth
Change the realloc() implementation to allow in-place growth. This is an important step in handling files without knowing their sizes a priori.
-rw-r--r--com32/lib/realloc.c72
1 files changed, 55 insertions, 17 deletions
diff --git a/com32/lib/realloc.c b/com32/lib/realloc.c
index cfbdc762..802d973c 100644
--- a/com32/lib/realloc.c
+++ b/com32/lib/realloc.c
@@ -4,16 +4,15 @@
#include <stdlib.h>
#include <string.h>
+#include <minmax.h>
#include "malloc.h"
-/* FIXME: This is cheesy, it should be fixed later */
-
void *realloc(void *ptr, size_t size)
{
- struct free_arena_header *ah;
+ struct free_arena_header *ah, *nah;
void *newptr;
- size_t oldsize;
+ size_t newsize, oldsize, xsize;
if ( !ptr )
return malloc(size);
@@ -23,26 +22,65 @@ void *realloc(void *ptr, size_t size)
return NULL;
}
- /* Add the obligatory arena header, and round up */
- size = (size+2*sizeof(struct arena_header)-1) & ARENA_SIZE_MASK;
-
ah = (struct free_arena_header *)
((struct arena_header *)ptr - 1);
- if ( ah->a.size >= size && size >= (ah->a.size >> 2) ) {
- /* This field is a good size already. */
+ /* Actual size of the old block */
+ oldsize = ah->a.size;
+
+ /* Add the obligatory arena header, and round up */
+ newsize = (size+2*sizeof(struct arena_header)-1) & ARENA_SIZE_MASK;
+
+ if ( oldsize >= newsize && newsize >= (oldsize >> 2) ) {
+ /* This allocation is close enough already. */
return ptr;
} else {
- /* Make me a new block. This is kind of bogus; we should
- be checking the adjacent blocks to see if we can do an
- in-place adjustment... fix that later. */
+ xsize = oldsize;
- oldsize = ah->a.size - sizeof(struct arena_header);
+ nah = ah->a.next;
+ if ((char *)nah == (char *)ah + ah->a.size &&
+ nah->a.type == ARENA_TYPE_FREE &&
+ oldsize + nah->a.size >= newsize) {
+ /* Merge in subsequent free block */
+ ah->a.next = nah->a.next;
+ ah->a.next->a.prev = ah;
+ nah->next_free->prev_free = nah->prev_free;
+ nah->prev_free->next_free = nah->next_free;
+ xsize = (ah->a.size += nah->a.size);
+ }
- newptr = malloc(size);
- memcpy(newptr, ptr, (size < oldsize) ? size : oldsize);
- free(ptr);
+ if (xsize >= newsize) {
+ /* We can reallocate in place */
+ if (xsize >= newsize + 2*sizeof(struct arena_header)) {
+ /* Residual free block at end */
+ nah = (struct free_arena_header *)((char *)ah + newsize);
+ nah->a.type = ARENA_TYPE_FREE;
+ nah->a.size = xsize - newsize;
+ ah->a.size = newsize;
+
+ /* Insert into block list */
+ nah->a.next = ah->a.next;
+ ah->a.next = nah;
+ nah->a.next->a.prev = nah;
+ nah->a.prev = ah;
- return newptr;
+ /* Insert into free list */
+ nah->next_free = __malloc_head.next_free;
+ nah->prev_free = &__malloc_head;
+ __malloc_head.next_free = nah;
+ nah->next_free->prev_free = nah;
+ }
+ /* otherwise, use up the whole block */
+ return ptr;
+ } else {
+ /* Last resort: need to allocate a new block and copy */
+ oldsize -= sizeof(struct arena_header);
+ newptr = malloc(size);
+ if (newptr) {
+ memcpy(newptr, ptr, min(size,oldsize));
+ free(ptr);
+ }
+ return newptr;
+ }
}
}