[comp.lang.c] Pointers To Structures Question

stan@Dixie.Com (Stan Brown) (03/03/91)

	I have a problem with a program that I am working on.   It compiles
and works correctly, but generates a compiler warning message of the form

warning : structure/union or structure/union pointer required

	Since I plan on moving this code to a new machine when I get it I
dam concerned aboyt portabilty.

	The environment that I am working in is a version 7 machine (Fortune
32:16 if you ever heard of it).

	What rellay pzzles me is that I think there is one more level of
inderction in the compare function than I think is required, yet it works.
If someone could explain my miscomception here, I would greatly
appreciate it.

** The structure

        /* structure of fileing location information    */
struct  file_loc  {
	char *key;
	char *description;
	char *long_description;
	int	qty;
	}  ;

** The array decleration

main(argc,argv)
int argc;
char *argv[];
{
struct file_loc *array_of_loc_struct[MAX_LOCS];

** The call from main

sort_loc(array_of_loc_struct);

** The call to qsort

void sort_loc(array)
struct file_loc *array[];
{
extern int cmp_loca();
int qty = 0;

	while(array[++qty] != NULL)        /* Find Qty */
		;

	 qsort(&array[0], qty, sizeof(struct series *), cmp_loca); 
}


** The compare function

int cmp_loca(struct1,struct2)
void  *struct1;
void  *struct2;
{
	return strcmp((*(struct file_loc *)struct1)->key, (*(struct file_loc *)struct2)->key);
}


-- 
Stan Brown	P. c. Design 	404-363-2303
ateche!sdba!stan				"vi forever"

scs@adam.mit.edu (Steve Summit) (03/03/91)

In article <7519@rsiatl.Dixie.Com> stan@Dixie.Com (Stan Brown) writes:
>	I have a problem with a program that I am working on.   It compiles
>and works correctly, but generates a compiler warning message of the form
>warning : structure/union or structure/union pointer required
>	Since I plan on moving this code to a new machine when I get it I
>dam concerned aboyt portabilty.

An excellent attitude.

It can be tricky to call qsort, and to write an appropriate
comparison function, correctly even in simple cases.  (For
instance, handing qsort an array of pointers to characters and
attempting to use strcmp as a comparison function is an obvious
and attractive technique that won't work, for reasons which I
ought to discuss in a future update to the FAQ list.)

Stan is trying to sort an array of pointers:

>	struct file_loc *array_of_loc_struct[MAX_LOCS];

to a structure:

>	struct  file_loc  {
>		char *key;
		...

with the call to qsort:

>	 qsort(&array[0], qty, sizeof(struct series *), cmp_loca); 

Here is a minor problem.  The array being sorted is an array of
pointers, so sizeof(struct *) is the right sort of number to be
passing to qsort as the element width.  However, it should be
sizeof(struct file_loc *), not sizeof(struct series *).  On
virtually all machines, all structure pointers (regardless of the
struct type pointed to) are, in some internal sense, "the same;"
in particular they tend all to be of the same size.  The
series/file_loc mismatch probably isn't causing any trouble, but
it should definitely be cleaned up.  (It's confusing, if nothing
else.)

A warning message is correctly generated within the comparison
routine, however:

>	int cmp_loca(struct1,struct2)
>	void  *struct1;
>	void  *struct2;
>	{
>		return strcmp((*(struct file_loc *)struct1)->key,
>					(*(struct file_loc *)struct2)->key);
>	}

This code manages not to make the most common qsort mistake,
which is to declare the two pointer arguments to the comparison
function as pointers to the item of interest.  They must, rather,
always be declared as pointers to void (or pointers to char,
under a pre-ANSI compiler).

qsort does not know what it is sorting or how big the elements
are.  (This is why it must be given the element width.)  It
operates exclusively with pointers, and can be used to sort
arrays of structures (where the element width would generally be
much larger than the size of a single int or pointer).  While
sorting, it does not pass, to the comparison routine, two
elements to be compared; it passes *pointers* to two elements
to be compared.  (Read it a second time.  You are in a twisty
little maze of "to"'s, all different, and significant.)

Here, however, we are sorting an array of pointers to structures,
so the pointers being passed to the comparison routine must be
considered as pointers to pointers to structures.

Let's look first at part of the comparison routine, as written:

>	void  *struct1;
>		return strcmp((*(struct file_loc *)struct1)->key,

struct1 is a pointer (a fairly undifferentiated pointer, which is
why it is a void *).  By putting the cast (struct file_loc *) in
front of it, we coerce it to a pointer to a struct file_loc.  The
* in front of the cast gives us a struct file_loc.  The struct
file_loc which we've arrived at then appears as the left-hand-
side of the -> operator.  But you're supposed to use . to pick
apart structures, not -> .  (-> is for *pointers* to structures.)
It is this mismatch that the compiler was complaining about.
What went wrong?

Let's back up.  The comparison routine was actually handed a
pointer to a pointer to a structure, coerced to a void *.
First, therefore, we should coerce it back to the pointer to a
pointer to a structure that it really is:

	(struct file_loc **)struct1

Putting a * in front:

	*(struct file_loc **)struct1

dereferences one pointer level; we are left with a pointer to a
struct file_loc, which is a perfectly acceptable left-hand-side
for the -> operator:

	(*(struct file_loc **)struct1)->key

(The extra parentheses are of course required because the
precedence of that leading * is lower than -> .)

Whenever I write a qsort comparison routine, it always strikes me
as being a little funny that I "put on" what looks like an extra
level of indirection in the cast (the (struct file_loc **), in
this case), only to take it right back "off" again with the
leading *.  But it's the way qsort comparison functions always
end up working, because they are handed a pointer to the things
they are to compare, not the things themselves.  (When the
things are themselves pointers, it's very easy to drop a level of
indirection in your thinking.)

I would recommend naming the two pointer parameters in the
comparison routine "p1" and "p2", rather than "struct1" and
"struct2", to minimize confusion.

                                            Steve Summit
                                            scs@adam.mit.edu

browns@iccgcc.decnet.ab.com (Stan Brown) (03/05/91)

In article <7519@rsiatl.Dixie.Com>, stan@Dixie.Com (Stan Brown) writes:
                                                    ^^^^^^^^^^

<<chuckle>>  Clone technology marches on!

My opinions are mine:  I don't speak for any other person or company.
                                   email: browns@iccgcc.decnet.ab.com
Stan Brown, Oak Road Systems, Cleveland, Ohio, USA    +1 216 371 0043