A pointer to void is a way of expressing a generic pointer type. In C, a pointer to any object of any type may be converted back and forth between a pointer to void implicitly without any loss of information.
So, you can do:
void *ptr;
int i=5;
float f=2.0;
double d=5.5;
char *s="string";
ptr=&i;
i=*ptr;
ptr=&f;
f=*ptr;
ptr=&d;
d=*ptr;
ptr=s;
s=ptr;
The value of i, f, d and s are guaranteed to be unaffected by the conversion to a void pointer and back again.
This capability makes a pointer to void a good candidate for the storage of generic objects as Matt describes in his post above.
Consider a simple stack implementation that makes no assumptions about the objects that will eventually be placed on the stack. This is a very powerful mechanism that can be applied to other ADTs such as linked lists, binary search trees, queues, etc.
/*** All code untested ***/
You might have a struct like this to represent the stack:
struct stack {
void **items; /* items in the stack */
int count; /* # of items in the stack */
int capacity; /* size of stack */
};
You might initialize the stack like this:
struct stack *
init(int capacity)
{
struct stack *obj=malloc(sizeof *obj);
if (obj) {
obj->items=malloc(sizeof *obj->items * capacity);
if (!obj->items) {
free(obj); obj=NULL;
} else {
obj->capacity=capacity;
obj->count=0;
}
}
return obj;
}
Then the obligatory "push" and "pop" functions:
int
push(struct stack *obj,void *item,size_t size)
{
if (obj->count==obj->capacity) {
/* grow stack here (see below) */
}
obj->items[obj->count]=malloc(size);
if (obj->items[obj->count]) {
memcpy(obj->items[obj->count],item,size);
++obj->count;
} else {
return -1; /* error */
}
return 0;
}
void *
pop(struct stack *obj)
{
void *item;
if (!obj->count)
return NULL;
/* Get the item at the top of the stack */
item=obj->items[obj->count-1];
/* Set the pointer that pointed to this item
* to NULL to "erase" the item from the stack */
obj->items[--obj->count]=NULL;
/* User must eventually free memory
* for item */
return item;
}
Now, a use for void ***. Above we omitted the code for growing a stack. Here's a function which might perform this task:
int
grow_stack(void ***items,int new_size)
{
char **tmp;
tmp=realloc(*items,new_size);
if (tmp) {
*items=tmp;
return 0;
} else {
return -1;
}
}
To call grow_stack() we need to pass a pointer to our stack items variable so that the realloc() call gets assigned to the *actual* items pointer rather than the local pointer in grow_stack(). We would call grow_stack() from our push() function like this (doubling the size of the stack):
if (grow_stack(&obj->items,obj->capacity*2)) {
/* success */
Also, look into the standard library function qsort(). Its final argument is a pointer to a function that accepts 2 const pointers to void(). This is because qsort() is a generic routine that can sort an array of objects of any type. This is made possible through void pointers.
Russ
bobbitts@hotmail.com