[comp.lang.c] syntax help

wayne@cs.odu.edu (C Wayne Huling) (03/21/91)

I keep getting errors from this, I don't understand the structure used
for namelist??  How do I access this?  I keep getting:

"t.c", line 14: warning: struct/union or struct/union pointer required

#include <stdio.h>
#include <sys/types.h>
#include <sys/dir.h>

main (){int numfiles,i;
	struct direct *(*namelist[100]);
	
	if ((numfiles = scandir(".",namelist,NULL,NULL)) == -1){
		fprintf (stderr,"ERROR: Couldn't open directory.\n");
		exit(1);
	} else printf ("Yeaaaa..... \n");
	fprintf(stderr,"%d \n",numfiles);
	for (i=0;i<=numfiles;i++)
		printf("%s\n",namelist[i]->d_name);
	fflush(stdout);
}

rjohnson@shell.com (Roy Johnson) (03/22/91)

In article <WAYNE.91Mar20115318@robyn.cs.odu.edu> wayne@cs.odu.edu (C Wayne Huling) writes:
> I keep getting errors from this, I don't understand the structure used
> for namelist??  How do I access this?  I keep getting:

> "t.c", line 14: warning: struct/union or struct/union pointer required
> [...]
>   struct direct *(*namelist[100]);
> [...]
>	printf("%s\n",namelist[i]->d_name);

namelist is array of pointers to pointers to struct direct, but you
are accessing it as if it's array of pointers to struct.  One more
level of indirection:
	printf("%s\n",(*namelist[i])->d_name);
should do it.

This post valid unless superceded by a post by Chris Torek, Doug Gwyn, or
Karl Heuer (where is he, anyway?), or other comp.lang.c gurus.
--
======= !{sun,psuvax1,bcm,rice,decwrl,cs.utexas.edu}!shell!rjohnson =======
Feel free to correct me, but don't preface your correction with "BZZT!"
Roy Johnson, Shell Development Company

torek@elf.ee.lbl.gov (Chris Torek) (03/22/91)

In article <WAYNE.91Mar20115318@robyn.cs.odu.edu> wayne@cs.odu.edu
(C Wayne Huling) writes:
>	struct direct *(*namelist[100]);
>	if ((numfiles = scandir(".",namelist,NULL,NULL)) == -1) ...

Chances are you wrote this based on a correct, but highly misleading
(BECAUSE IT USES EMPTY ARRAY BRACKETS! [okay, done shouting :-) ]),
version of the `scandir' manpage.  This particular version probably said:

	scandir(dirname, namelist, select, compar)
	char *dirname;
	struct direct *(*namelist[]);
	int (*select)();
	int (*compar)();

in its `SYNOPSIS' section.  Before you can use this manual page, you
must note two things:

 A. The man page shows the declaration in the callee, not the caller.
    For instance, when you see something like:

	fstat(fd, buf)
	int fd;
	struct stat *buf;

    you should think:

	routine_that_calls_fstat(int fd) {
		int result;
		struct stat st;

		result = fstat(fd, &st);
	}

    I.e., where the function demands a `pointer to T', you provide a
    pointer that already points to at least one instance of T.  (After
    all, if your pointer has not been set to point anywhere, why are
    you providing it at all?  If the callee is to make something up,
    it can bloody well make it up all by itself.)

 B. The declaration for `namelist' has empty array brackets.  RED ALERT!

Empty array brackets should always scare you.  You must examine them
twice, and then maybe twice more, to make sure they mean what you think
they mean.  Here is the declaration again:

	struct direct *(*namelist[]);

In this case, the empty array brackets are being used to `declare' the
argument to the scandir() function.  They are also the `innermost'
level, exactly as if the parentheses were not there.  In fact, the
parentheses are almost deliberately misleading, because there is a
different case where they are necessary:

	char (*p)[10];

declares `p' as a `pointer to array 10 of char', and the parentheses are
required to prevend the declaration from being `array 10 of pointer to
char'.

Anyway, since the parentheses are redundant, the first thing to do is
get rid of them---they are just a distraction.

	struct direct **namelist[];

Now we have what looks like `array ? of pointer to pointer to struct
direct'.  But we know (by The Rule) that there is no such thing as an
`array' parameter.  The compiler adjusts the declaration for any
parameter declared as `array N of T', and pretends you wrote `pointer
to T'.  (It is allowed, but not required, to drop the constant value N,
and as a result you, the programmer, are not required to include the
constant N.  This is a very special case.)

So the compiler will adjust this declaration as if we wrote `pointer to
T' instead of `array of T'.  Here `T' is `pointer to pointer to struct
direct', and the result is a `pointer to pointer to pointer to struct
direct', or

	struct direct ***namelist;

Finally (after about 60 lines) we know what it is that scandir really
wants: it wants a

	struct direct ***namelist;

Now you can go back to point `A' above: functions that expect a pointer
need one that points to something.  In order to have something for this
pointer to point to, we need to declare an object with one fewer `*'s:

	doit(char *dir) {
		struct direct **names;
		int i, nnames;

Now we can call scandir, passing the address of our `names' variable:

		nnames = scandir(dir, &names, NULL, NULL);

and scandir will then make `names' point to the first of `nnames'
objects, each of which is a `pointer to struct direct'.  Scandir will
further make each of those point to a single `struct direct' object.

		for (i = 0; i < nnames; i++)
			printf("%s\n", names[i]->d_name);

Scandir uses malloc() to allocate memory for each pointer, so to free
all allocated memory, we must first free each `struct direct' object:

		for (i = 0; i < nnames; i++)
			free(names[i]);

and then free the pointer-that-points-to-nnames-pointers-to-struct-direct:

		free(names);
	}

Note that we depend heavily on prototypes above; if we have not put the
proper prototypes in scope, we would have to write:

		nnames = scandir(dir, &names,
		    (int (*)())NULL, (int (*)())NULL);

and


		for (i = 0; i < nnames; i++)
			free((char *)names[i]);
		free((char *)names);

(this assumes that the lack of prototypes implies a lack of `void *';
by using the prototypes, we avoid the entire issue of the proper type
for free).

As a side note, the SunOS 4.1 scandir man page reads:

	scandir(dirname, &namelist, select, compar)
	char *dirname;
	struct direct **namelist;
	int (*select)();
	int (*compar)();

which is correct but is a complete change of perspective (now we are
viewing `scandir' from the caller, not from scandir itself), but also
goes on to write:

	alphasort(d1, d2)
	struct direct **d1, **d2;

when in fact `alphasort' (or any sort you give) is simply passed directly
to qsort, and therefore would have to be

	alphasort(d1, d2)
	void *d1, *d2;

to work correctly.  (This is not a problem on all the machines on which
SunOS runs, since they all have only one `kind' of pointer; and to be
fair, they inherited this particular error from 4BSD, although it has
since been fixed there.)

(One has to wonder why, when the scandir man page was changed to have
this new point of view, the corresponding section 2 man pages were not
changed the same way.  Certainly these should be consistent.)
-- 
In-Real-Life: Chris Torek, Lawrence Berkeley Lab CSE/EE (+1 415 486 5427)
Berkeley, CA		Domain:	torek@ee.lbl.gov