Tuesday, June 7, 2011

Words to Code By

"Any intelligent fool can make things bigger and more complex... It takes a touch of genius - and a lot of courage to move in the opposite direction." - Albert Einstein

Monday, June 6, 2011

Concatenation Buffer Implementation

As promised in my previous post, I will now present the implementation for a dynamic string buffer class which concatenates until reset() is called. We start with a typedef for the structure which contains the class members:


/* CB is for Concatenation Buffer, a dynamically sized null
 * terminated string buffer which accumulates until CB_reset() is
 * called.  It is particularly useful for things like creating
 * complex SQL queries.
 */

typedef struct _cb {
  size_t sz,
         len;
  char *buf;
} CB;

Explaining each of the members:

  • buf - points to the heap allocated memory in which we store our string.
  • len - current length of the string in our buffer.
  • sz - current size of our buffer, which must be able to hold the string and the null terminating byte on the end (len + 1).

Now, let's get started with the constructor:

CB* CB_constructor(CB *self, size_t sz_hint)
/***************************************************************
 * Prepare a CB for use with initial size of sz_hint.
 */
{
  CB *rtn= NULL;
  assert(sz_hint);
  self->sz= sz_hint;
  if(!(self->buf= malloc(self->sz))) goto abort;
  CB_reset(self);
  rtn= self;
abort:
  return rtn;
}

The first argument, self, is the address of the instance of CB which we will initiate. We can't know whether it resides on the stack, heap, or in the static data segment. sz_hint is the caller's hint as to how large the buffer should be sized initially. If it needs to get bigger, it will be realloc()'d on demand. We initiate the value of rtn to NULL to indicate failure. If everything goes OK, then it will be set to the value of self before the constructor returns. We use the assert() macro to verify that the caller did not pass us a 0 sz_hint. I am amazed at how many "C" programmers I have encountered who do not know about the assert() macro. Finally we malloc() the buffer memory and call our reset() function which sets len to 0 and null terminates the empty string.

The destructor is very simple:

void* CB_destructor(CB *self)
/****************************************************************
 * Free resources associated with CB.
 */
{
  if(self->buf) free(self->buf);   return self;
}

And we'll need a function to grow the buffer when required. Since this function is "private", we declare it static so that it is invisible outside of the current source file:

static int
growbuf(CB *self)
/****************************************************************
 * Attempt to increase the size buffer initially trying to double
 * it, then backing off 10% at a time.
 * Returns non-zero for error.  */
{
  int rtn= 1;
  size_t i;
  /* Initially try do double memory. If that fails, back off 10%
   * at a time
   */
  for(i= 20; i > 10; i--) {
    char *p;
    size_t new_sz= self->sz * i / 10;
    /* Try to reallocate the memory */
    if(!(p= realloc(self->buf, new_sz))) continue;
    self->buf= p;
    self->sz = new_sz;
    break;
  }
  if(i != 10) rtn= 0; /* Note success if we grew the buffer */
abort:
  return rtn;
}

And a sprintf() like variadic function used to build the string:

int
CB_sprintf(CB *self, const char *fmt, ...)
/****************************************************************
 * Same as sprintf, except you don't have to worry about buffer  * overflows.
 * Returns non-zero for error.
 */
{
  int is_done, rtn= -1;
  va_list arglist;
  /* Catch empty strings */
  if(!strlen(fmt)) return 0;
  /* vsprintf the fmt string */
  for(is_done= 0; !is_done;) {
    int rc;
    va_start (arglist, fmt);
    rc= vsnprintf(
        self->buf+self->len,
        self->sz - self->len,
        fmt,
        arglist);
     /* Buffer isn't large enough */
    if(rc >= (self->sz - self->len)) {
      if(growbuf(self)) is_done= 1;
    } else {
      if(rc != -1) { /* Successful return */
        rtn= rc;
        self->len += rc;
      }
      is_done= 1;
    }
    va_end (arglist);
  }
  return rtn;
}

With that I'll wrap up this post. Next time I will tackle simple inheritance. In the mean time, happy coding!