Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

C/C++

C List Manager


SP 89: C LIST MANAGER

Lisp-like lists in C

Bob is a software engineer for Halliburton Geophysical in Houston, Texas. This project was completed while still employed at Positron Corp. Bob can be reached at 2639 Valley Field Dr., Sugarland, TX 77479.


List management buys you a convenient way to create randomly accessible linked lists with low-storage overhead. The ability to store objects of any type generalizes the problem of list management so that each new problem doesn't find you writing virtually the same code over again (to handle a problem only moderately different from the previous one).

A list is defined as a collection of information of the same type, accessible in either sequential or random order. The type of information on the list can be anything, but within a given list, the type must be consistent (for example, all int, float, struct, pointers, and so on).

Generally, lists are represented as vectors of pointers to data. Most popular is probably the linked list, where each element of the list contains a pointer to the next element (a regular linked list), and possibly a pointer to the previous element (a doubly linked list).

In a language such as C, which provides for dynamic memory allocation, programmers generally opt for the linked list approach over pointer lists or arrays of fixed size. This allows the list to consume a minimum of memory to start with, and presents no artificial upper limit on the length of the list.

Traditionally, every new problem carries with it the burden of defining new data structures and building the linked list management code from scratch. This process is time-consuming, not to mention error-prone. The basic tasks required to implement a linked list are:

    1. Describe data structure for the linked list.

    2. Write code to add an element to the list. This involves building a new data structure, populating it, and linking it onto the list.

    3. Free all memory consumed in the linked list.

Wouldn't it be nice if linked lists could somehow be generalized, so that a canned package would easily handle current and future needs? That's what I'm presenting in this article -- a general list management system written in C that can hold any type of objects, even other lists. The coding was done under DOS 3.3 in Microsoft C 5.1, and the program has been compiled and run with no modifications on Unix System V, VMS 4.7, and a Sun 4/280. The source code for the program is presented in Listing One, page 90, while the header file is listed in Listing Two, page 91.

One thing many programmers have discovered is that although the general memory allocation routines offered with C (malloc, calloc, and so on) are very nice, they can be terribly inefficient for many problems. This is especially true when the memory allocation is performed in several small chunks (as is often the case when building linked lists): Each call to malloc takes time (to find the requested amount of memory) and actually consumes more memory than you requested (for bookkeeping overhead). For large projects, this can get so bad that overall performance of the application suffers greatly.

I have seen several programs gain substantial speed by adding a higher-level memory allocation routine. If the programmer finds himself constantly calling malloc to get chunks of memory of the same size, he can call this higher-level function to get memory from the memory allocator in larger chunks than needed. The higher-level allocator then doles out the space in smaller chunks as it is requested.

This limits the fragmentation of memory, and adds to the efficiency of the code. But, as with linked list implementations, the code to handle this must be written especially for each new application.

The list management system described here gives you this kind of higher-level memory allocator. To test the efficiency of the program, I took code that made many calls to malloc and replaced it with this list management system. The performance of the package increased by a factor of eight. You will realize better performance than this in some cases, worse in others.

The code generalizes the list management problem in such a way that problems of this kind are easily handled. If building and managing lists is very easy to do, you will find yourself using them in ways you never dreamed possible. You will not resist building a list as an elegant solution to a problem merely to avoid the coding complexity, or due to project time constraints.

I will first discuss the list management package from the subroutine level and then show you how it can be used. There will be enough short examples to serve as an easy go-by for virtually any project you can imagine. The examples will demonstrate how to make and maintain lists of numbers, strings, structures, lists, and functions.

The Program Interface

A whole suite of subroutines makes up this list management package. They are described in fairly general terms here.

As much as I tried to make the list management system problem independent, there is one small facet that must be considered each time based upon the problem at hand.

As mentioned earlier, calling malloc to get several small chunks of memory can be very wasteful. Because avoiding memory fragmentation was one of the design goals for this project, two items of information are required to set up a list:

    1. The size of each entry to be placed on the list.

    2. The number of entries to grab at a time with each internal call to malloc.

The choice of the number of entries is generally problem dependent, and it is the toughest choice you will have to make. Note that the number of entries presents no upper bound on the size of the list. It is merely used for the efficient allocation of memory.

A list is created using makelist. The return value is a void pointer, which is passed on to other routines in the package. Consider the return value from makelist like a FILE pointer. You needn't be concerned with what it points to.

After a list has been created, data is put onto the list using appendlist. List data always gets added to the end of the list in this implementation. appendlist requires two arguments: The list pointer to which the data is being appended, and a pointer to the data to be put on the list. Because appendlist expects a pointer to the data to be put on the list, and because it knows the size of the data item (you provided that in the call to makelist), it performs a copy of the pointed-to data into the internal data space it has reserved.

It is important to remember that the pointed-to data is copied. If you are putting integers onto the list, the integers themselves will be copied. There is no reason for the data being put on the list to be static, in this case. If you are appending pointers to strings, or pointers to structures, these pointers must point to malloced or static data space, if you expect to be able to recover the data from the list. At the very least, the data must remain static throughout the life of the list.

You might think that having to have malloced data space to put on a list is wasteful, as I claim that this package makes memory allocation more efficient. In reality, the package is a list management tool that manages memory for lists much more efficiently than you could do with malloc. A clever user could use the same tool for getting data space for his string storage (if that is what the list must contain).

Getting data off the list is easy. You have two choices: fetchlist allows you to get data items off the list in random order (as though the list were an array). fetchlist provides array-bound checking, and NULL is returned if your indices go outside the limits of the list being accessed.

walklist allows you to walk down the list, from beginning to end, without regard for list indices. walklist expects you to traverse the entire list. If you happen to find what you are looking for before walklist is done, you can use rewindlist to reset internal pointers so that walklist will start at the beginning on the next call.

On the first call, walklist always starts reading at the beginning of the list. It will return subsequent entries in the list until the end of the list is seen or a call is made to rewindlist. Calling fetchlist does not interfere with walklist calls. When walklist gets to the end of the list, NULL is returned.

What walklist and fetchlist return are void pointers to the internal data space containing your data. To actually get at the data can be a little tricky. For this reason, I have provided examples of storing and retrieving many different kinds of things on lists. The easiest thing to remember is this: What you get back is a pointer to a data area where your data resides. You must first cast this pointer to be what you expect and, in general, dereference the result.

If you stored integers, for example, the pointer returned would be a pointer to an integer. Cast it as such, then dereference to get the integer value. This is demonstrated in Example 1.

Example 1: A list of integers

  #ifdef Explanation
  -------------------------------------------------------------------------
  Make a list to hold integers, and put 100 integers onto the list.   Then, play back the list.
  -------------------------------------------------------------------------

  #endif

  #include <stdio.h>
  #include "makelist.h"

  main ()  {
       void *list,*dp;
       int  ii;
       /* make a list to hold integers */
        list = makelist (sizeof (int),10);
       /* put 100 integers on list */
       for (ii =0 ; ii < 100 ; ii ++)
             appendlist (list,&ii);
       /* use fetchlist to read back list */
       for (ii=0 ; ii < 100 ; ii++)  {
             void *dp = fetchlist(list,ii);
                printf("Entry %2d = %d\n",ii,*(int *)dp);
       }
       freelist(list);
       }

If you stored a pointer to a string, the returned value is a pointer to a pointer to a string. This is demonstrated in Example 2. If you wish to store data structures on a list, what you get back will be a pointer to the saved data structure. All you need to do is cast the returned pointer -- you needn't dereference it. That is because the data pointer returned holds the actual data structure. This is demonstrated in Example 3.

Example 2: A list of strings

  #ifdef Explanation
---------------------------------------------------------------------------
  Demonstrates the putting of strings on a list and use of gclist.
---------------------------------------------------------------------------
  #endif

  #include <stdio.h>
  #include "makelist.h"

  extern char *strdup();

  main()  {
      char buffer[132];
      void *list,*dp,*ptr;
      int  ii;
      int  cnt;

      /* make list to hold strings */
      list = makelist (sizeof (char *), 10);
      /* build the list of strings */
      for (ii=0 ; ii < 30 ; ii++)  {
          sprintf (buffer, "text string %d",ii);
          dp = strdup (buffer);
          appendlist(list,&dp);

      }
      /* use waklist to view each string saved in list */
      while (dp = walklist(list))
            printf("%s\n*,* ((char **)dp));
      /* free up list, as well as all malloced data */
      gclist(list);
  }

Example 3: A list of structures

  #ifdef Explanation
  --------------------------------------------------------------------
  Saving and retrieving structures on lists.
  --------------------------------------------------------------------
  #endif

  #include <stdio.h>
  #include "makelist.h"
  extern char *strdup();
  /* here is our data structure */
  struct foo {
             char *name;
             int ndx;

  } foo;

  main() {
      void *list;
      void *vp;
      struct foo *fp;
      int  ii,jj,kk;
      char buffer[256];
      /* make list to hold struct foo */
      list = makelist (sizeof (struct foo), 10);
      /* build list of 30 instances of struct foo */
      for (ii=0 ; ii < 30 ; ii++) {
                sprintf (buffer, "foo element %d", ii);
                foo. name = strdup (buffer);
                foo.ndx = ii;
                appendlist (list,&foo);
  }
  /* play back list. note that vp is merely cast */
  while (vp=walklist (list)) {
            fp = (struct foo *)vp;
            printf("%2d) %s\n", fp->ndx, fp->name);
  }
  /* free up each string malloced during list creation */
  while (vp=walklist (list)) {
            fp = (struct foo *)vp;
            free(fp->name);
  }
  /* free the list itself */
  freelist (list);
  }

To free a list (return all memory used by the list to free store), call freelist. freelist makes no assumptions about the data you have saved on the list. If what you have saved is pointers to malloced data space, when the list goes away, so do your pointers to the malloced space. In this case, merely use walklist to traverse the list, freeing each malloced pointer as you go.

malloclist is a function that performs a traditional malloc, with the malloced data address automatically added onto a list. By utilizing this function, you can easily free up all memory malloced during a particular function by calling gclist. This is different from freelist in that all data in the list is assumed to be malloced, and it is freed before the list itself is freed. Example 4 shows how to use malloclist.

Example 4: Using malloclist ( )

  #ifdef Explanation
  ------------------------------------------------------------------------
  We use malloclist to get memory space, rather than malloc.  This will
  automatically append malloced data onto a list (which you must provide),
  so that a single call to gclist will free not only the list, but all
  malloced space as well.
  ------------------------------------------------------------------------
  #endif

  #include <stdio.h>
  #include "makelist.h"

  main() {
      void *list, *dp, *ptr;

      printf ("Exercising malloclist...\n");
      list = makelist (sizeof (void *),10);

      ptr = "string #1";
      dp = malloclist (list,strlen(ptr)+1);
      strcpy(dp,ptr);
      ptr = "string #2";
      dp = malloclist(list,strlen(ptr)+1);
      strcpy(dp,ptr);
      ptr = "string #3";
      dp = malloclist (list,strlen(ptr)+1);
      strcpy(dp,ptr);
      /* play back list */
      while (dp = walklist (list))
                 printf("%s\n",*(char **)dp);
      /* free up the list */
      gclist(list);
  }

pushlist and poplist allow you to easily use a list as a stack. toplist allows you to examine the topmost stack item (the last entry pushed onto the stack). NULL is returned if the list is empty. Using these functions is demonstrated in Example 5.

Example 5: Pushing and popping a list

  #ifdef Explanation
  --------------------------------------------------------------------
  Demonstrate how to push & pop data off of a list.
  --------------------------------------------------------------------
  #endif

  #include <stdio.h>
  #include "makelist.h"

  main() {
       void *list, *dp, *ptr;
       int   ii,jj,kk;
       int   cnt;

       /* build list to hold integers */
       list = makelist (sizeof(int),5);
       /* push 10 integers onto list */
       for (ii=0 ; ii < 10 ; ii++)
                 pushlist(list,&ii);
       /* walk down list 10 times, popping an entry each time */
       for (ii=0 ; ii < 10 ; ii++) {
                 extern void *toplist ();
                 while (dp = walklist(list))
                    printf("%d\n",*(int *)dp);
              dp = toplist(list);
                 printf("popping off %d\n",*(int *)dp);
              poplist(list);
       }
       /* list is now empty */
       printf("pushing onto list again\n");
       for (ii=0 ; ii < 10 ; ii++)
                 pushlist(list,&ii);
       /* verify entire list */
       while (dp = walklist(list))
                 printf("%d\n",*(int *)dp);
       /* pop off an item at a time off list */
       for (ii=0 ; ii < 10 ; ii++) {
                 extern void *toplist();
                 dp = toplist(list);
                 printf("popping off %d\n",*(int *)dp);
                 poplist(list);
       }
       /* ensure list is indeed empty */
       printf("walking list again... should be empty\n");
       while (dp = walklist(list))
                 printf("%d\n",*(int *)dp);
       freelist(list);
  }

As an experiment, I added Lisp-like property manipulation to the stack. (Lisp allows you to associate a property with any data value. In Lisp, a property has a textual name and a value, which can be anything.)

In my implementation, a property is like an environment variable in DOS or Unix, where there is a name and an associated value (both of which are strings). For property lists, each property is associated with a single data item, and different data items may have the same property name with different values. There is no conflict.

When data is put onto a list (using appendlist, for example), a pointer is returned. This pointer is a pointer to the actual storage location for the data. This pointer is used to associate a property with a data item (for either saving or retrieving property information). Many properties may be associated with a single data item. The property value may be passed as NULL. Effectively, this just hangs a string name onto the specified data item.

To append data onto the list and associate property information with it simultaneously, use the function pappendlist. In its most common usage, this function allows you to hang names on data values as they are put on the list, which can be a very powerful feature.

To find a property on a list, use findprop. This function will find the first occurrence of a property with the specified name, and return a pointer to the data to which that property has been associated.

Property list handling is not done as efficiently as it could be, but it will do for small applications. Basic property list usage is demonstrated in Example 6 . Property list usage using pappendlist is shown in Example 7.

Example 6: Property list usage

  #ifdef Explanation
  ----------------------------------------------------------------------
  Put 100 integers on a list.  Every 5th element on the list, add a   special property value.
  ----------------------------------------------------------------------
  #endif

  #include <stdio.h>
  #include "makelist.h"

  extern char *strdup();

  main() {
      void *list,*dp,*ptr;
      int   jj;
      int   cnt;
      void *nl = makelist(sizeof(int),20);

      for (jj=0 ; jj < 100 ; jj++) {
                void *dp = appendlist(nl,&jj);
                /* every 5th element, put something on property list */
                if (!(jj%5)) {
                             char buffer[80];
                             sprintf(buffer,"list[%d]",jj);
                   putproplist(nl,dp,"MSG",strdup(buffer));
                }
      }
      for (jj=0 ; jj < 100 ; jj++) {
                void *dp = fetchlist(nl,jj);
                char *ptr;
                printf("%d\n",*(int *)dp);
                if (!(jj%5)) {
                             void *p = (jj) ? dp : (void *)NULL;
                             printf("PROP: %s\n",ptr = getproplist
                                                     (nl,p,"MSG"));
                             free(ptr);
                }
      }
      freelist(nl);
  }

Example 7: Property list usage w/pappendlist

  #ifdef Explanation
  ----------------------------------------------------------------------
  Make a list to hold integers, and put 100 integers onto the list.
  Then, play back the list.  For each integer put on the list, add
  a property value which uniquely identifies the data value.
  ----------------------------------------------------------------------
  #endif

  #include <stdio.h>
  #include "makelist.h"

  extern char *strdup();

  main() {
       void *list, *dp;
       char *ptr,buffer[80];
       int   ii;

       list = makelist(sizeof(int),10);

       /* put 100 integers on list, w/ prop to tell their index in ASCII */
       for (ii=0 ; ii < 100 ; ii++) {
                 sprintf(buffer,"index %d",ii);
                 pappendlist(list,&ii,"OP",strdup(buffer));
       }
       /* get each integer off list, and show its property */
       for (ii=0 ; ii < 100 ; ii++) {
                 void *dp = fetchlist(list,ii);
                 ptr = getproplist(list,dp,"OP");
                 printf("Entry %2d = %d, prop = %s\n",ii,*(int *)dp,ptr);
                 free(ptr);
       }
       freelist(list);
  }

Creative List Usage

After reviewing a few of the examples, you should realize that the list management package is easy to use. The biggest stumbling block is remembering always to pass pointers for information to be put onto the list, and to be careful in casting and dereferencing the items returned off the list. Once you get the hang of this, usage is a snap.

The simplest use for lists is for saving data in malloced data space much more efficiently than malloc alone can do. Speed-ups of a factor of eight of this package over malloc are fairly typical.

An interesting use of stacks is to initialize a list of lists. Build a list to hold your data, and then push the list onto the list of lists to save context. This is useful in recursive contexts, where a lot of dynamic information must be saved during recursion. An example of how a list of lists can be built and manipulated is shown in Example 8.

Example 8: A list of lists

  #ifdef Explanation
  ------------------------------------------------------------------------
  Build and populate a list of lists
  ------------------------------------------------------------------------
  #endif

  #include <stdio.h>
  #include "makelist.h"

  main() {
       void *listlist;
       int   ii,jj,kk;

       /* create a list of lists */
       listlist = makelist(sizeof(void *),10);
       /* build 10 lists to hold integers */
       for (ii=0 ; ii < 10 ; ii++) {
                 void *vp = makelist(sizeof(int),10);
                 appendlist(listlist,&vp);
       }
       /* populate each of the 10 lists */
       for (ii=0 ; ii < 10 ; ii++) {
                 void *list = *(void **)fetchlist(listlist,ii);
                 for (jj=0 ; jj < 20 ; jj++) {
                               kk = ii*100 + jj;
                               appendlist(list,&kk);
                 }
       }
       /* replay each of the 10 lists */
       for (ii=0 ; ii < 10 ; ii++) {
                 void *list = *(void **)fetchlist(listlist,ii);
                 for (jj=0 ; jj < 20 ; jj++)
                              printf("List %d, data = %d\n",ii,*(int
                                             *)fetchlist (list,jj));
       }
       /* free all lists */
       for (ii=0 ; ii < 10 ; ii++) {
                 void *list = *(void **)fetchlist(listlist,ii);
                 freelist(list);
       }
       freelist(listlist);
  }

One utility for property lists is building a list that contains a variety of different pointers (pointers to different things). This does not violate our definition of a list: Even though the pointers point to different things, the list is still merely a list of pointers. You can use the property tag to tell what type of pointer each list item is within the function that must interpret the list.

Real-World List Usage

As far as I am concerned, two types of functions are developed during any programming project. They are:

    1. Throw-away routines, written to make your code cleaner and easier to understand.

    2. Routines that are well thought-out and have usefulness beyond the scope of the current project (here, I invoke the buzzword "reusable").

Functions of the second type will make life easier for you down the road. They will provide you with a new set of tools for future development, and they have already been tested and debugged.

The real world can often be quite different, though. Most programmers have been at the following design crossroad. You have a project that you need to get working and time is running out. Meeting project deadlines can often impair your ability to dedicate the time required to generalize functions to the point that they are useful outside the scope of the current project.

A good programmer can easily tell what functionality lends itself to being made into a subroutine. He sees and anticipates the need for the special coding before he actually begins writing it. He will often ponder a few minutes, deciding what arguments need to be provided with the subroutine, and what the subroutine can return that will be most useful.

He will try to arrange the calling arguments in a reasonably logical fashion, so that it will be easy to remember how to call the function (without having to scrape around for the documentation). He tries to give the function a name that conveys the functionality, so that the subsequent coding which uses the subroutine will be easy to use, maintain, read, and understand.

As every experienced programmer knows, one of the best things that can come out of any programming project is a collection of useful subroutines that can be used in other projects down the road. But have you provided the generality necessary to make such a routine truly useful in a lot of different environments?

In some applications, it is often clear that what you have written will suffice for all applications one could envision. Standard library routines like strlen and strcat are good examples. Routines like sprintf are less clear. There is a reasonably good case to be made supporting the idea that what sprintf should return is a pointer to the string that it built, rather than the number of characters written. But it doesn't. You can do little about standard library routines.

But what about the examples where the task is not so clear-cut? Given the time constraints for a project, you may not be able to devote the time necessary to envision all of the possibilities, and code an appropriate solution.

Often, it will occur to you that there are features you'd like to add to your new function, but time constraints and satisfying the project goals limit your creativity. You know what has to be done now, and realize the potential of this routine in future applications.

Often the need arises to add extensibility to a function in a simple, straightforward manner, with no impact on existing code. This is a tough problem.

A good example is a menu function, wherein the user is presented a list of options on the screen. The topmost entry is highlighted. The user moves the cursor over the item of choice with the arrow keys on his keyboard. When the highlighted bar is over the appropriate menu item, he hits Enter.

Should the menu routine handle lists that are longer than the box on the screen? That requires extra code and extra time to write. Would you like to let the user leap to a menu entry by merely hitting the first character of the menu item? More code. Allow the user to fill in responses to certain menu items? Allow him to sort the menu a variety of ways? And on and on. More code.

How can you generalize the implementation so that gobs of code don't have to be replicated to handle future situations, while at the same time meeting your goal for getting your project out the door on time? Assume that you realize that a function you are coding will be expanded or enhanced at a later date. Merely add a void pointer as the last argument of the function call. In current usage (until you get the appropriate code written), merely pass a (void*) NULL as the final argument. You can even put out documentation, telling people that the final argument is required but is reserved for future expansion. You needn't tell them anything else at this point.

What could be better than a generic list to add as the final argument? Your code can always check for NULL as the last argument and take the appropriate default action. If the pointer is non-NULL, consider it a list, and interpret is as such.

This allows adding virtually endless expansion opportunities to a function without having to change a single line of existing code. And because you have used this list management package, the user isn't burdened with having to write any complicated code. His job is easier, maintenance headaches are lessened, and you can release a half-finished project before its time!

This approach is vastly different from the exec functions approach of merely passing a list of strings as arguments, representing argv to a function, where the last argument is a NULL pointer. Adding NULL as the final argument is an easy solution to simple problems. When the variety of information required to be passed to the function is more than simple strings, the problem becomes much more difficult.

In the menu example just mentioned, you might want to add arguments at a later date having to do with the menu-placement coordinates, how long the menu is allowed to be, the maximum width of the window, the functions to call when various keys are hit, and a slew of other parameters that can change how the menu works.

This is where a generic list would prove useful as the last argument of the subroutine calling sequence. A suggestion would be to create a list of void pointers (that is, pointers to anything). As an item is put on the list, give it a special property name with no property value. This tells the function what the item is. To facilitate this operation, use the pappendlist function.

In your application function which must deal with the list, you can easily search the list for a data item with a particular property name by calling findprop.

Another approach to adding extensibility to a function can be gotten by adding another suite of functions, which I'll call "preparatory functions." Preparatory functions are used to "set things up" in preparation for a call to the function you are actually interested in. You write each function to set certain internal static variables so that when your actual function of interest is called, the function performs to the user's specifications. This is not a new trick, although naming the preparatory functions can be rather ugly.

If preparatory functions are your choice for expandability of a given function, they can easily be handled by creating a list of functions. In Example 9, I show a function called proc. I have created an auxiliary function called f_proc. The application calls f_proc to get a list of the functions that are applicable to proc. f_proc builds a list, and puts the address of several functions on the list. It then returns a pointer to this list.

Example 9: A list of functions

  #ifdef Explanation
  -----------------------------------------------------------------------
  Demonstrate how to use build a list of functions and call them
  directly off of the list, with arguments.
  -----------------------------------------------------------------------
  #endif

  #include <stdio.h>
  #include <varargs.h>
  #include "makelist.h"

  /* This function expects integer arguments, zero terminated */
  static
  foo (argmark)
  va_list argmark; {
     int i;
     while (1) {
         i = va_arg (argmark, int);
         if (!i) break;
         printf ("%d\n", i);
     }
}

/* This function expects string arguments, NULL terminated */
static
goo (argmark)
va_list argmark; {
    char *ptr;
    while (1) {
    ptr = va_arg (argmark, char *);
    if (!ptr) break;
    printf ("%s\n", ptr);
    }
}

/* This function expects a string, followed by an integer */
static
poo (argmark)
va_list argmark; {
    char *ptr;
    int i;
    ptr = va_arg (argmark, char *);
    i = va_arg (argmark, int);
    printf (*string = %s, int = %d\n",ptr, i);
}

/* build list for functions internal to proc, and return pointer to list */
static void *flist = NULL;
void *
f_proc() {
    int (*func) ();
    if (!flist) flist = makelist (sizeof (int (*) ()); 10);
    /* put functions on flist, and give 'em names */
    func = foo; pappendlist (flist, &func, "FOO", NULL);
    func = goo; pappendlist (flist, &func, "GOO", NULL);
    func = poo; pappendlist (flist, &func, "POO", NULL);
    return flist;
}

/* out proc function */
void
proc () {
      printf ("In function proc\n");
}

main () {
      void *1_proc;
      /* get list of proc's functions */
      1_proc = f_proc ();
      /* invoke each of the functions with arguments */
      funclist (1_proc, "FOO", 1,2,3,4,0);
      funclist (1_proc, "GOO", "line 1", "line 2", "line 3", NULL);
      funclist (1_proc, "POO", "some text", 666);
      /* finally, call proc */
      proc ();
}

Each of the functions (foo, goo, and poo) declared static inside Example 9. This is not a requirement; it just makes for a cleaner interface.

Note that each of the three functions expects a variable length argument list, so varargs is used to get each argument. This is required because the user interface to these functions is made available indirectly through funclist. When funclist actually calls the function, it passes a pointer to the stack location of the argument list. If you aren't familiar with the varargs interface, the three provided functions should give you sufficient examples of its proper usage.

In the main routine in Example 9, you will see an example of how the user would access the extended functionality of the proc function. As seen in f_proc, the functions have been given a property tag which mnemonically indicates what the functions are. This makes for a cleaner, easier to understand interface for the user.

There is one caveat in providing an enhanced interface like this. If the function proc was initially designed to run without arguments (as in our example), be sure that it takes the default action as initially documented. If calling any of the preparatory functions will permanently modify the behavior of proc, be sure to tell the user about it. But by keeping the behavior predictable upon default, you will not need to recompile or relink any existing code to add functionality at a later date.

Summary

General list management is a useful addition to your arsenal of programming tools. Once you become adept at using the tools in this package, you will find yourself building and using lists in ways you never thought of. The best way to understand the package is to review the many examples.

_C LIST MANAGER_ by Robert Starr

[LISTING ONE]

<a name="02ac_0012">

/*   makelist- list management package
   RF Starr
   2639 Valley Field Dr.
   SugarLand, TX 77479
*/

#include <stdio.h>
#include <varargs.h>
#ifdef MSDOS
#include <stdlib.h>
#include <malloc.h>
#else
#define void char
extern char *malloc();
#endif

/*#define DEBUG*/

#ifdef DEBUG
#define Debug(x) x
#else
#define Debug(x)
#endif

typedef struct data DATA;
typedef struct list LIST;
typedef struct prop PROP;

struct data {
   void *data;   /* space for list->nentries instances of data */
   DATA *next;   /* next list->nentries collection of data */
};

struct prop {
   void *dataptr;   /* to what data item this property associates */
   void *propval;   /* property value to associate with the data */
   void *propsym;   /* optional symbol (usually char *) to associate */
   PROP *next;
};

struct list {
   int  entrysize;      /* size of each data entry in bytes */
   int  nentries;      /* # entries to grab per malloc call */
   int  empty_slots;   /* empty slots left in current data block */
   int  nitems;      /* total items saved in this list */
   int  ecount;      /* where we are when reading back list */
   int  fblock;
   DATA *fdata;
   PROP *prop;      /* optional property list for this list */
   DATA *data;      /* linked list for the actual data of this list */
   DATA *hidata;      /* highest allocated data block (for efficiency) */
};

/* Internal malloc routine */
/*#define MEMCHK*/
#ifdef MEMCHK
static FILE *memfp = NULL;
#endif
static void *
imalloc(size)
int size; {
   void *ptr = malloc(size);
#ifdef MEMCHK
   if (!memfp) memfp = fopen("meminfo","w");
#endif
   if (!ptr) {
      fprintf(stderr,"malloc error: no free memory left.\n");
      fflush(stderr);
   }
#ifdef MEMCHK
   fprintf(memfp,"%x malloc\n",ptr);
   fflush(memfp);
#endif
   return ptr;
}

static void *
ifree(addr)
void *addr; {
   free(addr);
#ifdef MEMCHK
   fprintf(memfp,"%x free\n",addr);
   fflush(memfp);
#endif
}

/* Build, initialize, and return an empty list */
void *
makelist(esize,nentries)
int esize,nentries; {
   LIST *list = imalloc(sizeof(LIST)+sizeof(DATA)+esize*nentries);
   void *dp   = imalloc(sizeof(DATA)+esize*nentries);
   if (!list || !dp) return NULL;
   list->data = (DATA *)dp;
   list->data->data = (char *)dp + sizeof(DATA);
   list->entrysize = esize;
   list->nentries = nentries;
   list->empty_slots = nentries;
   list->nitems = 0;
   list->ecount = 0;
   list->fblock = 0;
   list->fdata = NULL;
   list->prop = (PROP *)NULL;
   list->hidata = list->data;
   list->data->next = NULL;
   return (void *)list;
}

/*    Put items on property list for this data item.  Propsym is the
   property symbol, and val is a pointer to a _static_ are where the
   data for this property resides.
*/
putproplist(list,dataptr,propsym,val)
LIST *list;
void *dataptr,*val;
char *propsym; {
   PROP *newprop = (PROP *)imalloc(sizeof(PROP));
   PROP *topprop = list->prop;
   if (!list) return NULL;
   if (!newprop) return;
   newprop->dataptr = dataptr;
   newprop->propsym = propsym;
   newprop->propval = val;
   newprop->next = topprop;
   list->prop = newprop;
}

/*   Read an item off of the property list for a particular data
   item.  NULL returned if there is none.
*/
void *
getproplist(list,dataptr,propsym)
LIST *list;
void *dataptr,*propsym; {
   PROP *p;
   void *propval = NULL;
   if (!list) return NULL;
   p = list->prop;
   while (p) {
      int  fsym = !strcmp(p->propsym,propsym);
      if ((!dataptr && fsym) || (p->dataptr == dataptr && fsym)) {
         propval = p->propval;
         break;
      }

      p = p->next;
   }
   return propval;
}

/* Find data item associated with a property name */
void *
findprop(list,propsym)
LIST *list;
char *propsym; {
   PROP *p;
   if (!list) return NULL;
   p = list->prop;
   while (p) {
      if (!strcmp(p->propsym,propsym)) return p->dataptr;
      p = p->next;
   }
   return NULL;
}

/* Append data to the specified list */
static void
whereis(list,ndx,walk,put)
LIST *list;
int  *walk,*put; {
   *walk = ndx / list->nentries;
   *put  = ndx % list->nentries;
}

/* Remove last entry on the specified list... adjust struct accordingly */
void *
poplist(list)
LIST *list; {
   DATA *org;
   void *dp = NULL;
   unsigned char *data;
   int  put;
   if (!list) return NULL;
   if (!list->nitems) return dp;
   org = list->data;
   list->empty_slots++;
   list->nitems--;
   if (list->empty_slots == list->nentries) {
      while (org->next) org = org->next;
      put  = list->nitems % list->nentries;
      data = (unsigned char *)org->data;
      dp = (void *)(data+(list->entrysize*put));
      if (org->next) ifree(org->next), org->next = NULL;
      list->hidata = org;
   }
   return dp;
}

/* calculate data pointer for the ndx entry */
static void *
calcdp(list,ndx)
LIST *list;
int  ndx; {
   DATA *pdata;
   unsigned char *dp;
   int  size = (list) ? list->entrysize :0;
   int  max  = (list) ? list->nitems : 0;
   int  walk,put;
   if (!list || ndx >= max) return (void *)NULL;
   whereis(list,ndx,&walk,&put);
   pdata = list->data;
   while (walk--) pdata = pdata->next;
   dp = (unsigned char *)pdata->data;
   return (char *)(dp+(size*put));
}

/* Return pointer to data which is last on the list */
void *
toplist(list)
LIST *list; {
   void *dp = NULL;
   void *fetchlist();
   unsigned char *data;
   int put;
   if (!list || !list->nitems) return NULL;
   return calcdp(list,list->nitems-1);
}

/* append a data item onto specified list */
void *
appendlist(list,data)
LIST *list;

void *data; {
   DATA *pdata;
   void *where;
   unsigned char *dp;
   int  walk,put,size;
   if (!list) return NULL;
   size = list->entrysize;
   whereis(list,list->nitems,&walk,&put);
   pdata = list->hidata;
   if (!list->empty_slots) {
      void *mem = imalloc(sizeof(DATA)+size*list->nentries);
      if (!mem) return NULL;
      list->hidata = pdata = pdata->next = (DATA *)mem;
      pdata->data = (void *)((char *)mem+sizeof(DATA));
      pdata->next = NULL;
      list->empty_slots = list->nentries;
   }
   dp = (unsigned char *)pdata->data;
   where = (char *)(dp+(put*size));
   memcpy(where,data,size);
   list->empty_slots--;
   list->nitems++;
   return where;
}

/* return index of NEXT list entry */
listindex(list)
LIST *list; {
   return list->nitems;
}

/* append a data item onto specified list with property value information */
void *
pappendlist(list,data,propsym,val)
LIST *list;
void *data,*val;
char *propsym; {
   void *dataptr = appendlist(list,data);
   if (dataptr) putproplist(list,dataptr,propsym,val);
   return dataptr;
}

/* Just like appendlist... new name for compatibility with poplist */
void *
pushlist(list,data)
LIST *list;
void *data; {
   if (!list) return NULL;
   return appendlist(list,data);
}

/* Fetch a specific data item off of list... ndx is 0-based */
void *
fetchlist(list,ndx)
LIST *list;
int ndx; {
   DATA *pdata;
   unsigned char *dp;
   int  size = list->entrysize;
   int  max  = list->nitems;
   int  walk,put;
   if (!list) return NULL;
   if (ndx >= max) return (void *)NULL;
   whereis(list,ndx,&walk,&put);
   if (walk == list->fblock)
      list->fdata = pdata = (list->fdata) ? list->fdata : list->data;
   else {
      pdata = list->data;
      list->fblock = walk;
      while (walk--)
         pdata = pdata->next;
      list->fdata = pdata;
   }
   dp = (unsigned char *)pdata->data;
   Debug(printf("fetchlist: getting data from %x\n",dp+(size*put)));
   return (char *)(dp+(size*put));
}

/* reset pointer used by walklist */
rewindlist(list)
LIST *list; {
   if (list) list->ecount = 0;
}

/* walk down the list, returning each data item 'till there ain't no more */
void *
walklist(list)
LIST *list; {
   DATA *pdata;
   unsigned char *dp;
   int  size = list->entrysize;
   int  max  = list->nitems;
   int  index = list->ecount;
   int  walk,put;
   if (!list) return NULL;
   dp = (unsigned char *)fetchlist(list,index);
   if (!dp)
      list->ecount = 0;
   else
      list->ecount++;
   return (list->ecount) ? (void *)dp : (void *)NULL;
}

/* malloc size bytes, and add address of malloced space to the list */
void *
malloclist(list,size)
LIST *list;
int  size; {
   void *dp;
   if (!list) return NULL;
   dp = imalloc(size);
   if (dp) appendlist(list,&dp);
   return dp;
}

/* Free up all malloced data associated with the specified list */
freelist(list)
LIST *list; {
   fl(list,0);
}

/* garbage collect list... assume all data in list is malloced ptrs */
gclist(list)
LIST *list; {
   fl(list,1);
}

/* Free the list up.  If freedp != 0, free each data pointer as well */
static
fl(list,freedp)
LIST *list; {
   DATA *pdata,*ppd;
   PROP *p = list->prop;
   if (!list) return;
   pdata = list->data;
   if (freedp) {
      void *dp;
      rewindlist(list);
      while (dp=walklist(list))
         ifree(*(char **)dp);
   }
   /* free all malloced data */
   while (pdata) {
      DATA *next = pdata->next;
      ifree(pdata);
      pdata = next;
   }
   /* free all malloced property info */
   while (p) {
      PROP *n = p->next;
      ifree(p);
      p = n;
   }
   ifree(list);
}

/*    given a list of functions, invoke the function with the specified
   property tag with the arguments supplied.
*/

/*funclist(list,prop,args) (actual calling argument list)*/
int
funclist(va_alist)
va_dcl {
   void *list;
   void *prop;
   void *vp;
   int  (*func)();
   va_list argmark;
   va_start(argmark);
   list = va_arg(argmark,void *);
   prop = va_arg(argmark,void *);
   vp = findprop(list,prop);
   if (!vp) return 0;
   func = *(int (**)())vp;
   return (*func)(argmark);
}





<a name="02ac_0013"><a name="02ac_0013">
<a name="02ac_0014">
[LISTING TWO]
<a name="02ac_0014">

/* Header file for makelist */

/* Remove this if you don't have ANSI C compiler */
#define ANSIC

#ifndef ANSIC
#define void char
#endif

/* Usage: e.g. deref(int,x) or deref(char *,x) */
#define deref(type,x)  *((type*)(x))

/* Function prototypes/declarations */
/*----------------------------------------------------------------------*/
#ifdef ANSIC
extern  void *makelist(int esize,int nentries);
extern  int putproplist(void *list,void *dataptr,char *propsym,void *val);
extern  void *getproplist(void *list,void *dataptr,void *propsym);
extern  void *toplist(void *list);
extern  void *poplist(void *list);
extern  void *appendlist(void *list,void *data);
extern  void *pappendlist(void *list,void *data,char *propsym,char *val);
extern  void *pushlist(void *list,void *data);
extern  void *fetchlist(void *list,int ndx);
extern  void *walklist(void *list);
extern  void rewindlist(void *list);
extern  void *malloclist(void *list,int size);
extern  int freelist(void *list);
extern  int gclist(void *list);
extern  int funclist(void *,...);
extern  int listindex(void *list);
#else
extern  void *makelist();
extern  int putproplist();
extern  void *getproplist();
extern  void *toplist();
extern  void *poplist();
extern  void *appendlist();
extern  void *pappendlist();
extern  void *pushlist();
extern  void *fetchlist();
extern  void *walklist();
extern  void rewindlist();
extern  void *malloclist();
extern  int freelist();
extern  int gclist();
extern  int funclist();
extern  int listindex();
#endif
/*----------------------------------------------------------------------*/


Example 1: A list of integers


#ifdef Explanation
----------------------------------------------------------------------
Make a list to hold integers, and put 100 integers onto the list.
Then, play back the list.
----------------------------------------------------------------------
#endif

#include <stdio.h>
#include "makelist.h"

main() {
   void *list,*dp;
   int  ii;
   /* make a list to hold integers */
   list = makelist(sizeof(int),10);
   /* put 100 integers on list */
   for (ii=0 ; ii < 100 ; ii++)
      appendlist(list,&ii);
   /* use fetchlist to read back list */
   for (ii=0 ; ii < 100 ; ii++) {
      void *dp = fetchlist(list,ii);
      printf("Entry %2d = %d\n",ii,*(int *)dp);
   }
   freelist(list);
}


Example 2: A list of strings


#ifdef Explanation
----------------------------------------------------------------------
Demonstrates the putting of strings on a list and use of gclist.
----------------------------------------------------------------------
#endif

#include <stdio.h>
#include "makelist.h"

extern char *strdup();

main() {
   char buffer[132];
   void *list,*dp,*ptr;
   int  ii;
   int  cnt;
   /* make list to hold strings */
   list = makelist(sizeof(char *),10);
   /* build the list of strings */
   for (ii=0 ; ii < 30 ; ii++) {
      sprintf(buffer,"text string %d",ii);
      dp = strdup(buffer);
      appendlist(list,&dp);
   }
   /* use walklist to view each string saved in list */
   while (dp = walklist(list))
      printf("%s\n",*((char **)dp));
   /* free up list, as well as all malloced data */
   gclist(list);
}


 Example 3: A list of structures


#ifdef Explanation
----------------------------------------------------------------------
Saving and retrieving structures on lists.
----------------------------------------------------------------------
#endif

#include <stdio.h>
#include "makelist.h"

extern char *strdup();
/* here is our data structure */
struct foo {
   char *name;
   int  ndx;
} foo;

main() {
   void *list;
   void *vp;
   struct foo *fp;
   int  ii,jj,kk;
   char buffer[256];
   /* make list to hold struct foo */
    list = makelist(sizeof(struct foo),10);
   /* build list of 30 instances of struct foo */
   for (ii=0 ; ii < 30 ; ii++) {
      sprintf(buffer,"foo element %d",ii);
      foo.name = strdup(buffer);
      foo.ndx = ii;
      appendlist(list,&foo);
   }
   /* play back list.  note that vp is merely cast */
   while (vp=walklist(list)) {
      fp = (struct foo *)vp;
      printf("%2d) %s\n",fp->ndx,fp->name);
   }
   /* free up each string malloced during list creation */
   while (vp=walklist(list)) {
      fp = (struct foo *)vp;
      free(fp->name);
   }
   /* free the list itself */
   freelist(list);
}


Example 4: Using malloclist()

#ifdef Explanation
----------------------------------------------------------------------
We use malloclist to get memory space, rather than malloc.  This will
automatically append malloced data onto a list (which you must provide),
so that a single call to gclist will free not only the list, but all
malloced space as well.
----------------------------------------------------------------------
#endif

#include <stdio.h>
#include "makelist.h"

main() {
   void *list,*dp,*ptr;

   printf("Exercising malloclist...\n");
   list = makelist(sizeof(void *),10);

   ptr = "string #1";
   dp = malloclist(list,strlen(ptr)+1);
   strcpy(dp,ptr);
   ptr = "string #2";
   dp = malloclist(list,strlen(ptr)+1);
   strcpy(dp,ptr);
   ptr = "string #3";
   dp = malloclist(list,strlen(ptr)+1);
   strcpy(dp,ptr);
   /* play back list */
   while (dp = walklist(list))
      printf("%s\n",*(char **)dp);
   /* free up the list */
   gclist(list);
}


Example 5: Pushing and poping a list

#ifdef Explanation
----------------------------------------------------------------------
Demonstrate how to push & pop data off of a list.
----------------------------------------------------------------------
#endif

#include <stdio.h>
#include "makelist.h"

main() {
   void *list,*dp,*ptr;
   int  ii,jj,kk;
   int  cnt;

   /* build list to hold integers */
   list = makelist(sizeof(int),5);
   /* push 10 integers onto list */
   for (ii=0 ; ii < 10 ; ii++)
      pushlist(list,&ii);
   /* walk down list 10 times, popping an entry each time */
   for (ii=0 ; ii < 10 ; ii++) {
      extern void *toplist();
      while (dp = walklist(list))
         printf("%d\n",*(int *)dp);
      dp = toplist(list);
      printf("popping off %d\n",*(int *)dp);
      poplist(list);
   }
   /* list is now empty */
   printf("pushing onto list again\n");
   for (ii=0 ; ii < 10 ; ii++)
      pushlist(list,&ii);
   /* veryify entire list */
   while (dp = walklist(list))
      printf("%d\n",*(int *)dp);
   /* pop off an item at a time off list */
   for (ii=0 ; ii < 10 ; ii++) {
      extern void *toplist();
      dp = toplist(list);
      printf("popping off %d\n",*(int *)dp);
      poplist(list);
   }
   /* ensure list is indeed empty */
   printf("walking list again... should be empty\n");
   while (dp = walklist(list))
      printf("%d\n",*(int *)dp);
   freelist(list);
}


Example 6: Property list usage


#ifdef Explanation
----------------------------------------------------------------------
Put 100 integers on a list.  Every 5th element on the list, add a
special property value.
----------------------------------------------------------------------
#endif

#include <stdio.h>
#include "makelist.h"

extern char *strdup();

main() {
   void *list,*dp,*ptr;
   int  jj;
   int  cnt;
   void *nl = makelist(sizeof(int),20);

   for (jj=0 ; jj < 100 ; jj++) {
      void *dp = appendlist(nl,&jj);
      /* every 5th element, put something on property list */
      if (!(jj%5)) {
         char buffer[80];
         sprintf(buffer,"list[%d]",jj);
         putproplist(nl,dp,"MSG",strdup(buffer));
      }
   }
   for (jj=0 ; jj < 100 ; jj++) {
      void *dp = fetchlist(nl,jj);
      char *ptr;
      printf("%d\n",*(int *)dp);
      if (!(jj%5)) {
         void *p = (jj) ? dp : (void *)NULL;
         printf("PROP: %s\n",ptr = getproplist(nl,p,"MSG"));
         free(ptr);
      }
   }
   freelist(nl);
}


Example 7: Property list usage w/pappendlist


#ifdef Explanation
----------------------------------------------------------------------
Make a list to hold integers, and put 100 integers onto the list.
Then, play back the list.  For each integer put on the list, add
a property value which uniquely identifies the data value.
----------------------------------------------------------------------
#endif

#include <stdio.h>
#include "makelist.h"

extern char *strdup();

main() {
   void *list,*dp;
   char *ptr,buffer[80];
   int  ii;

   list = makelist(sizeof(int),10);

   /* put 100 integers on list, w/ prop to tell their index in ASCII */
   for (ii=0 ; ii < 100 ; ii++) {
      sprintf(buffer,"index %d",ii);
      pappendlist(list,&ii,"OP",strdup(buffer));
   }
   /* get each integer off list, and show its property */
   for (ii=0 ; ii < 100 ; ii++) {
      void *dp = fetchlist(list,ii);
      ptr = getproplist(list,dp,"OP");
      printf("Entry %2d = %d, prop = %s\n",ii,*(int *)dp,ptr);
      free(ptr);
   }
   freelist(list);
}


Example 8: A list of lists


#ifdef Explanation
----------------------------------------------------------------------
Build and populate a list of lists
----------------------------------------------------------------------
#endif

#include <stdio.h>
#include "makelist.h"

main() {
   void *listlist;
   int  ii,jj,kk;

   /* create a list of lists */
    listlist = makelist(sizeof(void *),10);
   /* build 10 lists to hold integers */
   for (ii=0 ; ii < 10 ; ii++) {
      void *vp = makelist(sizeof(int),10);
      appendlist(listlist,&vp);
   }
   /* populate each of the 10 lists */
   for (ii=0 ; ii < 10 ; ii++) {
      void *list = *(void **)fetchlist(listlist,ii);
      for (jj=0 ; jj < 20 ; jj++) {
         kk = ii*100 + jj;
         appendlist(list,&kk);
      }
   }
   /* replay each of the 10 lists */
   for (ii=0 ; ii < 10 ; ii++) {
      void *list = *(void **)fetchlist(listlist,ii);
      for (jj=0 ; jj < 20 ; jj++)
         printf("List %d, data = %d\n",ii,*(int *)fetchlist(list,jj));
   }
   /* free all lists */
   for (ii=0 ; ii < 10 ; ii++) {
      void *list = *(void **)fetchlist(listlist,ii);
      freelist(list);
   }
   freelist(listlist);
}


Example 9: A list of functions


#ifdef Explanation
----------------------------------------------------------------------
Demonstrate how to use build a list of functions and call them
directly off of the list, with arguments.
----------------------------------------------------------------------
#endif

#include <stdio.h>
#include <varargs.h>
#include "makelist.h"

/* This function expects integer arguments, zero terminated */
static
foo(argmark)
va_list argmark; {
   int  i;
   while (1) {
      i = va_arg(argmark,int);
      if (!i) break;
      printf("%d\n",i);
   }
}

/* This function expects string arguments, NULL terminated */
static
goo(argmark)
va_list argmark; {
   char *ptr;
   while (1) {
      ptr = va_arg(argmark,char *);
      if (!ptr) break;
      printf("%s\n",ptr);
   }
}

/* This function expects a string, followed by an integer */
static
poo(argmark)
va_list argmark; {
   char *ptr;
   int  i;
   ptr = va_arg(argmark,char *);
   i = va_arg(argmark,int);
   printf("string = %s, int = %d\n",ptr,i);
}

/* build list for functions internal to proc, and return pointer to list */
static void *flist = NULL;
void *
f_proc() {
   int  (*func)();
   if (!flist) flist = makelist(sizeof(int (*)()),10);
   /* put functions on flist, and give 'em names */
   func = foo; pappendlist(flist,&func,"FOO",NULL);
   func = goo; pappendlist(flist,&func,"GOO",NULL);
   func = poo; pappendlist(flist,&func,"POO",NULL);
   return flist;
}

/* our proc function */
void
proc() {
   printf("In function proc\n");
}

main() {
   void *l_proc;
   /* get list of proc's functions */
   l_proc = f_proc();
   /* invoke each of the functions with arguments */
   funclist(l_proc,"FOO",1,2,3,4,0);
   funclist(l_proc,"GOO","line 1", "line 2", "line 3", NULL);
   funclist(l_proc,"POO","some text", 666);
   /* finally, call proc */
   proc();
}











Copyright © 1989, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.