We want to test our code to make sure that it always releases allocated
memory and that it behaves robustly when memory allocations fail. We
can do the former by building our own memory manager that keeps tracks
of blocks as they are allocated and freed. The memory manager can also
disallow allocations according to a policy set by the user, taking care
of the latter.
The available policies are:
/* Memory tracking policy. */
enummt_policy {MT_TRACK, /* Track allocation for leak detection. */
MT_NO_TRACK, /* No leak detection. */
MT_FAIL_COUNT, /* Fail allocations after a while. */
MT_FAIL_PERCENT, /* Fail allocations randomly. */
MT_SUBALLOC /* Suballocate from larger blocks. */
};
MT_TRACK and MT_NO_TRACK should be self-explanatory.
MT_FAIL_COUNT takes an argument specifying after how many allocations
further allocations should always fail. MT_FAIL_PERCENT takes an
argument specifying an integer percentage of allocations to randomly
fail.
MT_SUBALLOC causes small blocks to be carved out of larger ones
allocated with malloc(). This is a good idea for two reasons:
malloc() can be slow and malloc() can waste a lot of space dealing
with the small blocks that libavl uses for its node. Suballocation
cannot be implemented in an entirely portable way because of alignment
issues, but the test program here requires the user to specify the
alignment needed, and its use is optional anyhow.
The memory manager keeps track of allocated blocks using structblock:
/* Memory tracking allocator. */
/* A memory block. */
structblock {structblock *next; /* Next in linked list. */
intidx; /* Allocation order index number. */
size_tsize; /* Size in bytes. */
size_tused; /* MT_SUBALLOC: amount used so far. */
void *content; /* Allocated region. */
};
See also @refalso{127
This code is included in @refalso{97
The next member of structblock is used to keep a linked list of all
the currently allocated blocks. Searching this list is inefficient, but
there are at least two reasons to do it this way, instead of using a
more efficient data structure, such as a binary tree. First, this code
is for testing binary tree routines--using a binary tree data structure
to do it is a strange idea! Second, the ISO C standard says that, with
few exceptions, using the relational operators (<, <=, >, >=) to
compare pointers that do not point inside the same array produces
undefined behavior, but allows use of the equality operators (==, !=)
for a larger class of pointers.
We also need a data structure to keep track of settings and a list of
blocks. This memory manager uses the technique discussed in
Exercise 2.5-3 to provide this structure to the allocator.
/* Indexes into arg[] within structmt_allocator. */
enummt_arg_index {MT_COUNT = 0, /* MT_FAIL_COUNT: Remaining successful allocations. */
MT_PERCENT = 0, /* MT_FAIL_PERCENT: Failure percentage. */
MT_BLOCK_SIZE = 0, /* MT_SUBALLOC: Size of block to suballocate. */
MT_ALIGN = 1 /* MT_SUBALLOC: Alignment of suballocated blocks. */
};
/* Memory tracking allocator. */
structmt_allocator {structlibavl_allocatorallocator; /* Allocator. Must be first member. */
/* Settings. */
enummt_policypolicy; /* Allocation policy. */
intarg[2]; /* Policy arguments. */
intverbosity; /* Message verbosity level. */
/* Current state. */
structblock *head, *tail; /* Head and tail of block list. */
intalloc_idx; /* Number of allocations so far. */
intblock_cnt; /* Number of still-allocated blocks. */
};
Function mt_create() creates a new instance of the memory tracker. It
takes an allocation policy and policy argument, as well as a number
specifying how verbose it should be in reporting information. It uses
utility function xmalloc(), a simple wrapper for malloc() that
aborts the program on failure. Here it is:
staticvoid *mt_allocate (structlibavl_allocator *, size_t);
staticvoidmt_free (structlibavl_allocator *, void *);
/* Initializes the memory manager for use
with allocation policy policy and policy arguments arg[],
at verbosity level verbosity, where 0 is a ``normal'' value. */
structmt_allocator * mt_create (enummt_policypolicy, intarg[2], intverbosity) {structmt_allocator *mt = xmalloc (sizeof *mt);
mt->allocator.libavl_malloc = mt_allocate;
mt->allocator.libavl_free = mt_free;
mt->policy = policy;
mt->arg[0] = arg[0];
mt->arg[1] = arg[1];
mt->verbosity = verbosity;
mt->head = mt->tail = NULL;
mt->alloc_idx = 0;
mt->block_cnt = 0;
returnmt;
}
After allocations and deallocations are done, the memory manager must be
freed with mt_destroy(), which also reports any memory leaks. Blocks
are removed from the block list as they are freed, so any remaining
blocks must be leaked memory:
/* Frees and destroys memory tracker mt, reporting any memory leaks. */
void mt_destroy (structmt_allocator *mt) {assert (mt != NULL);
if (mt->block_cnt == 0) {if (mt->policy != MT_NO_TRACK && mt->verbosity>= 1)
printf ("Nomemoryleaks.\n");
} else {structblock *iter, *next;
if (mt->policy != MT_SUBALLOC) printf ("Memoryleaksdetected:\n");
for (iter = mt->head; iter != NULL; iter = next) {if (mt->policy != MT_SUBALLOC)
printf ("block#%d:%lubytes\n",
iter->idx, (unsignedlong) iter->size);
next = iter->next;
free (iter->content);
free (iter);
}}free (mt);
}
For the sake of good encapsulation, mt_allocator() returns the structlibavl_allocator associated with a given memory tracker:
/* Returns the structlibavl_allocator associated with mt. */
void * mt_allocator (structmt_allocator *mt) {return &mt->allocator;
}
The allocator function mt_allocate() is in charge of implementing the
selected allocation policy. It delegates most of the work to a pair of
helper functions new_block() and reject_request() and makes use of
utility function xmalloc(), a simple wrapper for malloc() that
aborts the program on failure. The implementation is straightforward:
/* Creates a new structblock containing size bytes of content
and returns a pointer to content. */
staticvoid * new_block (structmt_allocator *mt, size_tsize) {structblock *new;
/* Allocate and initialize new structblock. */
new = xmalloc (sizeof *new);
new->next = NULL;
new->idx = mt->alloc_idx++;
new->size = size;
new->used = 0;
new->content = xmalloc (size);
/* Add block to linked list. */
if (mt->head == NULL)
mt->head = new;
else mt->tail->next = new;
mt->tail = new;
/* Alert user. */
if (mt->verbosity>= 3)
printf ("block#%d:allocated%lubytes\n",
new->idx, (unsignedlong) size);
/* Finish up and return. */
mt->block_cnt++;
returnnew->content;
}
/* Prints a message about a rejected allocation if appropriate. */
staticvoid reject_request (structmt_allocator *mt, size_tsize) {if (mt->verbosity>= 2)
printf ("block#%d:rejectedrequestfor%lubytes\n",
mt->alloc_idx++, (unsignedlong) size);
}
/* Allocates and returns a block of size bytes. */
staticvoid * mt_allocate (structlibavl_allocator *allocator, size_tsize) {structmt_allocator *mt = (structmt_allocator *) allocator;
/* Special case. */
if (size == 0)
returnNULL;
switch (mt->policy) {caseMT_TRACK: returnnew_block (mt, size);
caseMT_NO_TRACK: returnxmalloc (size);
caseMT_FAIL_COUNT:
if (mt->arg[MT_COUNT] == 0) {reject_request (mt, size);
returnNULL;
}mt->arg[MT_COUNT]--;
returnnew_block (mt, size);
caseMT_FAIL_PERCENT:
if (rand () / (RAND_MAX / 100 + 1) <mt->arg[MT_PERCENT]) {reject_request (mt, size);
returnNULL;
}else returnnew_block (mt, size);
caseMT_SUBALLOC:
if (mt->tail == NULL||mt->tail->used+size> (size_t) mt->arg[MT_BLOCK_SIZE])
new_block (mt, mt->arg[MT_BLOCK_SIZE]);
if (mt->tail->used+size<= (size_t) mt->arg[MT_BLOCK_SIZE]) {void *p = (char *) mt->tail->content+mt->tail->used;
size = ((size+mt->arg[MT_ALIGN] - 1)
/ mt->arg[MT_ALIGN] * mt->arg[MT_ALIGN]);
mt->tail->used+= size;
if (mt->verbosity>= 3)
printf ("block#%d:suballocated%lubytes\n",
mt->tail->idx, (unsignedlong) size);
returnp;
}else fail ("blocksize%lutoosmallfor%lu-byteallocation",
(unsignedlong) mt->tail->size, (unsignedlong) size);
default: assert (0);
}}
The corresponding function mt_free() searches the block list for the
specified block, removes it, and frees the associated memory. It
reports an error if the block is not in the list:
/* Releases block previously returned by mt_allocate(). */
staticvoid mt_free (structlibavl_allocator *allocator, void *block) {structmt_allocator *mt = (structmt_allocator *) allocator;
structblock *iter, *prev;
/* Special cases. */
if (block == NULL||mt->policy == MT_NO_TRACK) {free (block);
return;
}if (mt->policy == MT_SUBALLOC)
return;
/* Search for block within the list of allocated blocks. */
for (prev = NULL, iter = mt->head; iter; prev = iter, iter = iter->next) {if (iter->content == block) {
/* Block found. Remove it from the list. */
structblock *next = iter->next;
if (prev == NULL)
mt->head = next;
else prev->next = next;
if (next == NULL) mt->tail = prev;
/* Alert user. */
if (mt->verbosity>= 4)
printf ("block#%d:freed%lubytes\n",
iter->idx, (unsignedlong) iter->size);
/* Free block. */
free (iter->content);
free (iter);
/* Finish up and return. */
mt->block_cnt--;
return;
}}
/* Block not in list. */
printf ("attempttofreeunknownblock%p(alreadyfreed?)\n", block);
}
Please take a moment to fill out
this visitor survey You can help support this site by
visiting the advertisers that sponsor it! (only once each, though)