How do you do this???

Chris Torek chris at mimsy.umd.edu
Sat Dec 9 09:32:51 AEST 1989


In article <167 at salt.UUCP> snow at china.uu.net (John Snow) writes:
>I am writing a program where I need to dynamically allocate memory for
>an array of strings (ie list[x][10]), but I can't seem to please the
>compiler.  I want to be able to use it in the form:
>
>for (x = 0; x < n; x++)
>	strcpy (list[x], source[x]);

(`list' should almost certainly be `char **'.)

>As a first shot I tried:
>
>typedef ARRAY[][10];
>...
>{
>ARRAY list;  /* tried both 'list' and '*list' */

A good compiler would complain about this definition.  It defines an
array of unknown size (i.e., most likely zero elements), each element
of which is an array-10-of-char.  Making it a pointer is no help, as
it then points to zero or more contiguous arrays of unknown size.

>... I finally found one that at least compiles and runs, even
>though the compiler does gripe about it.  That is:
>
>{
>char (*list)[10];
>
>	list = calloc (5, 10);
>...
>}

As declared here, `list' is a pointer to zero or more contiguous arrays
of ten characters.  The call to calloc is missing a cast, and blithely
assumes sizeof(char)==1 (fairly safe these days), but is otherwise correct.
It allocates 5 contiguous arrays of ten characters each, and sets `list'
to point to the first (0'th).

	list = (char (*)[10])calloc(5, sizeof(char [10]));
 /* or	   ... (char (*)[10])calloc(5, sizeof(*list)); */
 /* (which is identical) */

>Even thought this *seems* to work, I don't have a warm-fuzzy because
>of the compiler warning.  Could somebody tell me how to do this in a
>clean way?

. . . and the wheel of history turns round again . . .

From: chris at mimsy.UUCP (Chris Torek)
Subject: Re: char ***pointer;
Keywords: allocating space
Date: 18 Nov 88 07:40:26 GMT

	char *p;

declares an object p which has type `pointer to char' and no specific
value.  (If p is static or external, it is initialised to (char *)NULL;
if it is automatic, it is full of garbage.)  Similarly,

	char **p;

declares an object p which has type `pointer to pointer to char' and
no specific value.  We can keep this up for days :-) and write

	char *******p;

which declares an object p which has type `pointer to pointer ... to char'
and no specific value.  But we will stop with

	char ***pppc;

which declares `pppc' as type `pointer to pointer to pointer to char',
and leaves its value unspecified.  None of these pointers point *to*
anything, but if I say, e.g.,

	char c = '!';
	char *pc = &c;
	char **ppc = &pc;
	char ***pppc = &ppc;

then I have each pointer pointing to something.  pppc points to ppc;
ppc points to pc; pc points to c; and hence, ***pppc is the character
'!'.

Now, there is a peculiar status for pointers in C: they point not only
to the object immediately at *ptr, but also to any other objects an
an array named by *(ptr+offset).  (The latter can also be written as
ptr[offset].)  So I could say:

	int i, j, k;
	char c[NPPC][NPC][NC];
	char *pc[NPPC][NPC];
	char **ppc[NPPC];
	char ***pppc;

	pppc = ppc;
	for (i = 0; i < NPPC; i++) {
		ppc[i] = pc[i];
		for (j = 0; j < NPC; j++) {
			pc[i][j] = c[i][j];
			for (k = 0; k < NC; k++)
				c[i][j][k] = '!';
		}
	}

What this means is perhaps not immediately clear%.  There is a two-
dimensional array of pointers to characters pc[i][j], each of which
points to a number of characters, namely those in c[i][j][0] through
c[i][j][NC-1].  A one-dimensional array ppc[i] contains pointers to
pointers to characters; each ppc[i] points to a number of pointers to
characters, namely those in pc[i][0] through pc[i][NPC-1].  Finally,
pppc points to a number of pointers to pointers to characters, namely
those in ppc[0] through ppc[NPPC-1].
-----
% :-)
-----

The important thing to note is that each variable points to one or
more objects whose type is the type derived from removing one `*'
from the declaration of that variable.  (Clear? :-)  Maybe we should
try it this way:)  Since pppc is `char ***pppc', what ppc points to
(*pppc) is of type `char **'---one fewer `*'s.  pppc points to zero
or more objects of this type; here, it points to the first of NPPC
objects.

As to malloc: malloc obtains a blob of memory of unspecified shape.
The cast you put in front of malloc determines the shape of the blob.
The argument to malloc determines its size.  These should agree, or you
will get into trouble later.  So the first thing we need to do is
this:

	pointer = (char ***)malloc(N * sizeof(char **));
	if (pointer == NULL) quit("out of memory... goodbye");

Pointer will then point to N objects, each of which is a `char **'.
None of those `char **'s will have any particular value (i.e., they
do not point anywhere at all; they are garbage).  If we make them
point somewhere---to some object(s) of type `char **'---and make
those objects point somewhere, then we will have something useful.

Suppose we have done the one malloc above.  Then if we use:

	pointer[0] = (char **)malloc(N1 * sizeof(char *));
	if (pointer[0] == NULL) quit("out of memory");

we will have a value to which pointer[0] points, which can point to
N1 objects, each of type `char *'.  So we can then say, e.g.,

	i = 0;
	while (i < N1 && fgets(buf, sizeof(buf), input) != NULL)
		pointer[0][i++] = strdup(buf);

(strdup is a function that calls malloc to allocate space for a copy
of its string argument, and then copies the string to that space and
returns the new pointer.  If malloc fails, strdup() returns NULL.)
We could write instead

	i = 0;
	while (i < N1 && fgets(buf, sizeof(buf), input) != NULL)
		*(*pointer)++ = strdup(buf);

Note that

		**pointer++ = strdup(buf);

sets **pointer (equivalently, pointer[0][0]), then increments the
value in `pointer', not that in pointer[0].  But using *(*pointer)++
means that we will later have to write

	pointer[0] -= i;

to adjust pointer[0] backwards by the number of strings read in and
strdup()ed, or else use negative subscripts to locate the strings.

Probably all of this will be somewhat clearer with a more realistic
example.  The following code creates an array of arrays of lines.

/* begin code (untested) */
/* this assumes prototypes are available */

#include <stddef.h>
#include <stdio.h>
#include <string.h>

static char nomem[] = "out of memory, exiting";

quit(char *msg) {
	(void) fprintf(stderr, "%s\n", msg);
	exit(1);
	/* NOTREACHED */
}

/*
 * Read an input string from a file.
 * Return a pointer to dynamically allocated space.
 */
char *readstr(FILE *f) {
	register char *s = NULL, *p;
	int more = 1, curlen = 0, l;
	char inbuf[BUFSIZ];

	/*
	 * The following loop is not terribly efficient if you have
	 * many long input lines.
	 */
	while (fgets(inbuf, sizeof(inbuf), f) != NULL) {
		p = strchr(inbuf, '\n');
		if (p != NULL) {	/* got it all */
			*p = 0;
			l = p - inbuf;
			more = 0;	/* signal stop */
		} else
			l = strlen(inbuf);

		/*
		 * N.B. dpANS says realloc((void *)NULL, n) => malloc(n);
		 * if your realloc does not work that way, you will
		 * have to fix this.
		 */
		s = realloc(s, curlen + l + 1);
		if (s == NULL)
			quit(nomem);
		strcpy(s + curlen, inbuf);
		if (more == 0)		/* done; stop */
			break;
		curlen += l;
	}
	/* should check for input error, actually */
	return (s);
}

/*
 * Read an array of strings into a vector.
 * Return a pointer to dynamically allocated space.
 * There are n+1 vectors, the last one being NULL.
 */
char **readfile(FILE *f) {
	register char **vec, *s;
	register int veclen;

	/*
	 * This is terribly inefficent, but it should be correct.
	 *
	 * malloc below is implicitly cast to (char **), but this
	 * depends on it returning (void *); old compilers need the
	 * cast, since malloc() returns (char *).  The same applies
	 * to realloc() below.
	 */
	vec = malloc(sizeof(char *));
	if (vec == NULL)
		quit(nomem);
	veclen = 0;
	while ((s = readstr(f)) != NULL) {
		vec = realloc(vec, (veclen + 2) * sizeof(char *));
		if (vec == NULL)
			quit(nomem);
		vec[veclen++] = s;
	}
	vec[veclen] = NULL;
	return (vec);
}

/*
 * Read a list of files specified in an argv.
 * Each file's list of lines is stored as a vector at p[i].
 * The end of the list of files is indicated by p[i] being NULL.
 *
 * It would probably be more useful, if less appropriate
 * for this example, to return a list of (filename, contents) pairs.
 */
char ***readlots(register char **names) {
	register char ***p;
	register int nread;
	register FILE *f;
	char **vp;
	extern int errno;

	p = malloc(sizeof(char **));
	if (p == NULL)
		quit(nomem);
	for (nread = 0; *names != NULL; names++) {
		if ((f = fopen(*names, "r")) == NULL) {
			(void) fprintf(stderr, "ThisProg: cannot read %s: %s\n",
				*names, strerror(errno));
			continue;
		}
		vp = readfile(f);
		(void) fclose(f);
		p = realloc(p, (nread + 2) * sizeof(char **));
		if (p == NULL)
			quit(nomem);
		p[nread++] = vp;
	}
	p[nread] = NULL;
	return (p);
}

/* e.g., instead:
struct file_data {
	char	*fd_name;
	char	**fd_text;
};
struct file_data *readlots(register char **names) {
	register struct file_data *p;
	register int nread;
	register FILE *f;
	char **vp;
	extern int errno;

	p = malloc(sizeof(*p));
	if (p == NULL)
		quit(nomem);
	for (nread = 0; *names != NULL; names++) {
		<...same file-reading code as above...>
		p = realloc(p, (nread + 2) * sizeof(*p));
		if (p == NULL)
			quit(nomem);
		p[nread].fd_name = *names;
		p[nread].fd_text = vp;
		nread++;
	}
	p[nread].fd_name = NULL;
	p[nread].fd_text = NULL;
	return (p);
}
*/
/* end of code */
-- 
In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 7163)
Domain:	chris at cs.umd.edu	Path:	uunet!mimsy!chris



More information about the Comp.lang.c mailing list