Monday, May 16, 2011

Static Initialization

For cases where the number of instances (per thread) of a class are known at compile time, it is most efficient to place them in static data segment. C and C++ provide static initialization facilities for structures and simple data types where the values are known at compile time. A more general solution to initializing static instances is to call the class constructor, and the C++ startup code does just that before the main() function is called. This strategy does not work, however, for shared objects which are loaded during runtime.

An even more general approach is to arrange for the class constructor to be called on each static instance of the class exactly once per process before the instance is used. This could be done like so:

void someFunc(void)
{
  static int is_initialized= 0;
  static CLASS s_class;

  if(!is_initialized) {
    is_initialized= 1;
    CLASS_constructor(&s_class);
  }

  /* do stuff with s_class */
}

This is not thread-safe. Most compilers have a keyword which causes a separate static instance or your data to exist in each thread. For gcc, the keyword is "__thread". So, a thread-safe version for GCC is:

void someFunc(void)
{
  static __thread int is_initialized= 0;
  static __thread CLASS s_class;

  if(!is_initialized) {
    is_initialized= 1;
    CLASS_constructor(&s_class);
  }

  /* do stuff with s_class */
}

For many classes this pattern of initialization occurs frequently enough that it deserves it's own initializer. Here is an example of a static initializer which calls the class constructor the first time, and the class reset() function thereafter:

typedef struct _CLASS {
  int is_initialized;
  /* Other members go here */
} CLASS;

void CLASS_sinit(CLASS *self)
{
  if(!self->is_initialized) {
    CLASS_constructor(self);
    self->is_initialized= 1;
  } else {
    CLASS_reset(self);
  }
}

Note that CLASS_sinit() takes advantage of the fact that all uninitialized static data is guaranteed to be zero (or NULL for pointers). Here is how you would use CLASS_sinit():

void someFunc(void)
{
  static __thread CLASS s_class;
  CLASS_sinit(&s_class);

  /* do stuff with s_class */
}

It is important to note that while using static instances this way is thread-safe, it is not reentrant, and probably shouldn't be used in source code intended for libraries.

Now it's time to start putting together this post with the previous posts to create a useful dynamic string buffer class. In order to improve the usability of the class, I have chosen to make the contents accumulate until the reset() function is called, and so it's really a concatenation buffer, or CB for short. Here is the class declaration:

#include <stdarg.h>
#include <stdlib.h>

/* 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;

#define CB_str(self) \
  ((const char*)(self)->buf)
/****************************************************************
 * Return the pointer to the buffer.
 */

#define CB_len(self) \
  ((self)->len)
/****************************************************************
 * Return the current length of the string in the buffer
 */

#define CB_reset(self) \
  ((self)->buf[((self)->len= 0)]= '\0')
/****************************************************************
 * Reset the buffer so that the length is zero.
 */

int CB_sinit(CB *self, size_t sz_hint);
/****************************************************************
 * Initialization to be called for static instances of CB each
 * use, but actual initialization only occurs once.
 */

CB* CB_constructor(CB *self, size_t sz_hint);
/****************************************************************
 * Prepare a CB for use with initial size of sz_hint.
 */

#define CB_create(p)\
   ((p)= (CB_constructor((p)=malloc(sizeof(CB))) ? \
     (p) : ( p ?  realloc(CB_destructor(p),0): 0)))


void* CB_destructor(CB *self);
/****************************************************************
 * Free resources associated with CB.
 */
#define CB_destroy(self)\
  free(CB_destructor(self))

int CB_sprintf(CB *self, const char *fmt, ...);
/****************************************************************
 * Same as sprintf, except you don't have to worry about buffer
 * overflows.  Returns -1 for error.
 */

int CB_vsprintf(CB *self, const char *fmt, va_list ap);
/****************************************************************
 * Same as vsprintf, except you don't have to worry about buffer
 * overflows.  Returns -1 for error.
 */

In my next post I will go over the implementation of the CB class, which is quite simple.

No comments:

Post a Comment