Programming Pointers

Into, but not out of, the void

Dan Saks

6/10/2008 12:00 AM EDT

The early dialect of C described by Kernighan and Ritchie (Kernighan, Brian W. and Ritchie, Dennis M., The C Programming Language. Prentice Hall, 1978.) (often called "Classic C") didn't include the keyword void, and therefore had no type void *. Lacking void *, Classic C programs tended to use char * as the type for a "generic" data pointer--a pointer that can point to any data object. Unfortunately, this increased the need to use cast expressions.

For example, Classic C didn't include the now standard memory management functions malloc and free. On page 175 of their book, Kernighan and Ritchie implemented their own allocation function, alloc, which they defined as:

char *alloc(bytes)
unsigned bytes;
{
    ...
}

This function definition is in the old, non-prototyped style of Classic C. In Standard C, the equivalent declaration would be:

char *alloc(unsigned bytes);

This alloc function returns the address of the allocated storage as a pointer of type char *. However, they designed alloc to allocate storage for any type of object, not just char or array of char. When allocating storage for something other than a char (or array thereof), the program must convert the char * result into a pointer to the intended type of the allocated object--a conversion that requires a cast. Kernighan and Ritchie used casts to convert the result of calling alloc to something other than char *, in functions such as:

struct tnode *talloc()
{
   return ((struct tnode *)alloc(sizeof(struct tnode)));
}


Next:




McObject

6/13/2008 6:47 PM EDT

Good article, Dan. While void pointers certainly cleaned up the language, and they’re treatment in C++ makes it more difficult to mess up, there’s another way in which they’re used (also, char * in the K&R days) that is even more insidious than your example.

It’s common for middleware vendors, especially embedded database vendors (I’ll pick on them because that the industry I have worked in for almost 20 years), to publish a generic programming interface that is used regardless of what is actually being stored in a specific database. In other words, I would use the same generic function to write a customer record, vendor record, employee record, and so on. The prototype of that function might look like
unsigned int write_record( …, void *data, …);
And my application could invoke in a manner similar to
unsigned in rc;
…
rc = write_record( …, cust_data, …);
…
Because any type of pointer can be assigned to a void pointer, the compiler and the database run-time are happy. But, if I accidentally pass another type of pointer when I’ve set the database up to expect a pointer to customer data, things go badly (database corruption, a crash, or both).

The solution is, of course, to avoid generic programming interfaces, which is the approach we’ve taken with the native programming interface for eXtremeDB. Much like CORBA IDL, the eXtremeDB native programming interface is a by-product of compiling the database’s data definition language, so we generate a “customer_new()” function for which the prototype dictates a handle (pointer) to a customer. If the programmer makes a mistake and passes any other type, the program won’t compile. Poof! A whole class of very common database application programming errors is eliminated.

This simple principle can, and should, be followed by every vendor and every C/C++ programmer that is creating a library of general-purpose routines.

For a bit more detail, see the article, http://www.eetimes.com/in_focus/embedded_systems/OEG20030818S0082.

Sign in to Reply



Please sign in to post comment

Navigate to related information

Datasheets.com Parts Search

185 million searchable parts
(please enter a part number or hit search to begin)
Jobs sponsored by

Feedback Form