I concur strongly with Derek's sentiments and would add some more comments:

I often use custom memory allocators to help debug wild pointer errors and 
rather than zeroing out allocated memory, I will often write a repeating 
multibyte sentinal value (such as 0xbaad) into the memory block so that 
it's easy to tell from a memory dump that something accessed an 
uninitialized memory block. When performance counts, this can be disabled 
for release builds or replaced with memset(dest,0,count), but I rarely find 
that initializing freshly allocated memory is a big performance bottleneck.

It can also be useful (at least in debug builds) to have xmalloc allocate 
extra memory before the beginning and after the end of a requested block, 
filling it with different sentinals and checking (in x_free) for pointers 
writing off either end of the block. Finally, when I free a block, I 
overwrite it with yet another sentinal, signifying freed memory, so that I 
can easily identify wild-pointer bugs due to aliased pointers pointing to 
freed memory (e.g., char *p, *q, c; p = malloc(10); q = p; strcpy(q,"foo"); 
free(p); c = *q;).

example (Caution: untested example code, not production quality):

static const char block_init[] = {'\xba', '\xad'};
static const size_t block_init_len = sizeof(block_init);

static const char block_header_padding[] = {'\x6b', '\x6b', '\x6b', '\x6b', 
'\x6b', '\x6b', '\x6b', '\x6b',
                                             '\x6b', '\x6b', '\x6b', 
'\x6b', '\x6b', '\x6b', '\x6b', '\x6b' };
static const char block_end_padding[] =    {'\xb6', '\xb6', '\xb6', '\xb6', 
'\xb6', '\xb6', '\xb6', '\xb6',
                                             '\xb6', '\xb6', '\xb6', 
'\xb6', '\xb6', '\xb6', '\xb6', '\xb6' };
static const char block_padding_len = sizeof(block_header_padding);

static const char freed_block_stuffing[] = {'\xf0', '\x0f'};
static const size_t freed_block_stuffing_len = sizeof(freed_block_stuffing);

void *
xmalloc(size_t size)
{
     size_t i = 0;
     void * ptr = NULL;
     char * dest = NULL;

#if defined(NO_ZERO_BYTE_ALLOCATIONS)
     /* see my notes below on whether this should be treated as a bug */
     assert(size > 0);
     if (size == 0) {
         char buf[80];
         sprintf (buf, "internal domain error: attempt to allocate zero 
bytes");
         error (1, 0, buf);
         }
#endif  /* NO_ZERO_BYTE_ALLOCATIONS */

     const size_t block_len = size + 2 * block_padding_len + sizeof(size_t);
     ptr = malloc(block_len);
     if (ptr == NULL) {
         char buf[80];
         sprintf (buf, "out of memory; can not allocate %lu bytes",
             (unsigned long) block_len);
         error (1, 0, buf);
         }
     /* initialize block with "uninitialized block" sentinal value */
     for (i = 0, dest = (char *)ptr + sizeof(size_t) + block_padding_len;
             i + block_init_len <= size; /* funky phrasing of comparison
                                          * to avoid subtraction of unsigned
                                          * values: avoid underflow errors
                                          * if size < block_init_len!
                                          */
             i+= block_init_len, dest += block_init_len) {
         memcpy(dest, block_init, block_init_len);
         }
     /* stash nominal block length (without padding) for use in finding 
tail padding in x_free */
     *(size_t *)ptr = size;
     /* initialize head and tail padding blocks to check for array index 
overflow or underflow */
     memcpy((char *)ptr + sizeof(size_t), block_header_padding, 
block_padding_len);
     memcpy((char *)ptr + sizeof(size_t) + block_padding_len + size, 
block_end_padding, block_padding_len);
     return (void *)((char *)ptr + sizeof(size_t) + block_padding_len);
}

void
x_free(void * ptr)
{
     size_t i = 0, len = 0;
     char * dest = NULL;
     void * base = NULL;

     assert(ptr != NULL);
     if (ptr == NULL) {
         char buf[80];
         sprintf (buf, "attempt to free null pointer");
         error (1, 0, buf);
         }
     base = (void *)( (const char *)ptr - sizeof(size_t) - block_padding_len)
     len = *(const size_t *)base;
     /* check for integrity of head and tail padding blocks */
     if (memcmp( (const char *)ptr - block_padding_len, 
block_header_padding, block_padding_len)) {
         char buf[80];
         sprintf (buf, "block header padding overwritten");
         error (1, 0, buf);
         }
     if (memcmp( (const char *)ptr + len,
             block_end_padding, block_padding_len)) {
         char buf[80];
         sprintf (buf, "block end padding overwritten");
         error (1, 0, buf);
         }
     /* fill with freed-block sentinal... */
     for (dest = (char *)base, i = 0;
             i + freed_block_stuffing_len <= len;  /* funky phrasing of 
comparison
                                                    * to avoid subtraction 
of unsigned
                                                    * values: avoid 
underflow errors
                                                    * if len < 
freed_block_stuffing_len!
                                                    */
             i+= freed_block_stuffing_len, dest += freed_block_stuffing_len) {
         memcpy(dest, freed_block_stuffing, freed_block_stuffing_len);
         }
     free( (void *)((const char *)ptr - sizeof(size_t) - block_padding_len));
}

If code were written perfectly, we wouldn't have wild-pointer bugs and 
these techniques would be unnecessary, but in the real world, we all see 
wild-pointer errors and these ideas (adapted from Robert Ward's old (out of 
print, alas!) book, "Debugging C") have served me quite well for many years.

As to non-portability, this code compiles without errors or warnings with 
strict ANSI compiler switches.

As to the question of requests to allocate zero bytes, I would consider 
those to be domain errors. A well-written program should not ask for 
allocations of zero bytes. Following this line, I would write xmalloc to 
consider such a request erroneous, and to generate a run-time error.

However, comments in the xmalloc code in CVS note that some places allocate 
zero-byte blocks and free them. I grepped the code for "alloc *( *0" and 
turned up nothing, so could someone clue me in on:
* Where zero-byte requests are made,
* Whether this represents good coding or sloppy coding,
* and Whether such requests should be considered bugs to be fixed?

Just My HO,
Jonathan

At 05:58 PM 5/2/00, Derek Scherger wrote:

> > > For safety, I propose that xmalloc zero out the memory it allocates.  Any
> > > comments or rebuttals?
> > >
> > For safety, I would prefer it does not.  I don't think we should
> > use non-portable solutions to cover up the faults of badly-written
> > software.  I'd feel more comfortable using the software knowing that
> > some classes of errors would have been discovered.
>
>So you would rather have problems occur *randomly* depending on what the
>values *happen* to be at the moment? Personally, if something is not
>properly initialized I'd like a core dump every time so that it is found
>and fixed quickly.

===========================================================================
Jonathan M. Gilligan                     <[EMAIL PROTECTED]>
Research Assistant Professor of Physics                      (615) 343-6252
Dept. of Physics and Astronomy, Box 1807-B                    Fax: 343-7263
Stevenson Center 6823
Vanderbilt University, Nashville, TN 37235           Dep't Office: 322-2828

Reply via email to