[comp.lang.c] Question: C string/structure confusion

wkaufman@oracle.oracle.com (William P. Kaufman) (04/07/90)

In article <78932@tut.cis.ohio-state.edu> <waylonis@cis.ohio-state.edu> writes:
>typedef struct menu_struct
>{
>	int	num_items;
>	char	**item; 	/* this may be wrong, but I want a arbitrary
>				   number of items of arbitrary length */
>} menu_td;
>
>menu_td  menu[5];		/* want 5 menus */
>
>	menu[0].item[0] = "Item0";

	Don't tell me: the compiler accepts it, but it pukes at run-time.

	OK, the trick I've found to dealing with obscure structures is to strip
down what you want, and figure out its type.

	menu is an array of menu_td, so menu[0] is valid, and it's a menu_td.
	menu[0].item then is valid, and its a (char **), so that assignment
_looks_ fine.  But, since it's a _pointer_ and not an _array_, menu[0].item
points to random space: it needs to be allocated!

	    menu[0].item =
		(char **)malloc( (size_of_item_array) * (sizeof(char *)) );

	Can't stress strongly enough--if
		a) you need a list of stuff (this is true for any type)
		b) either the number of stuff is constant, or it's range is
			known and is small
	and	c) you don't need off-the-cuff assignment of the stuff pointe

typedef struct menu_struct
{
	int	num_items;
	char	*item[MAXITEM];
} menu_td;

If you've got a good idea of how many items you'll have (say, within a tenth
of the actual number), and you don't have any cases where you might want to
assign a pre-assigned array of strings to item, this might be better.  Hey,
you see how many posts we get on this one subject alone?

Practice safe programming!  Even if it might cause you to waste a couple of
megs of memory,...memory's cheap!  But, I'm not.

			-- Bill Kaufman.
			{...}!hplabs!oracle.com!wkaufman
			.sig under construction

chris@mimsy.umd.edu (Chris Torek) (04/08/90)

In article <78932@tut.cis.ohio-state.edu>
dan_waylonis@gryphon.cis.ohio-state.edu writes:
>I am having a problem with loading a structure with a string:

No, actually, you are having a different problem.  Anyway:

>typedef struct menu_struct {
>	int	num_items;
>	char	**item; 	/* this may be wrong, but I want a arbitrary
>				   number of items of arbitrary length */
>} menu_td;

The type for `item' is the best one for `zero or more pointers to char',
each of which points to zero or more characters.  If `item' is set to
the address of the first of a sequence of pointers, each of which points
to the first of a sequence of characters that are ended by a '\0', then
`item[i]' (for any valid, i.e., in-range, integral i) can be called a
`string of arbitrary length'.

>menu_td  menu[5];		/* want 5 menus */
>
>	menu[0].item[0] = "Item0";
>
>But this last line doesn't work...

This is a type mismatch.  menu[0].item[0] has type `pointer to char';
its value is that of the first of the pointers to which menu[0].item
has been set to point.  Since menu[0].item has not been set to point
anywhere, this value is either garbage (if menu[] is an automatic varible)
or a nil of type `char **' (if menu[] is a global or static variable).
In either case, it does not point to at least one pointer.

So, the first problem is to set menu[i] (for 0 <= i <= 4) so that it
has a valid `num_items' field and a valid `items' field:

	int i;

	for (i = 0; i < 4; i++) {
		menu[i].num_items = 0;
		menu[i].items = NULL;
	}

Now each menu has zero items, and a nil pointer of type `char **' (no
matter which of the two proper standard definitions of `NULL' are used).
(This loop is unnecessary if menu[] is global or static.  It is also
unnecessary if you can prove that menu[i] will not be used before it
is set to some valid value.)

Next, suppose that menu[0] has a fixed size, 4, and should have 4 `items':

	menu[0].num_items = 4;

menu[0].items must now be set to point to the first of at least four
contiguous objects of type `pointer to char'.  One easy way to do this
is:

	{
		static char *menu0[4] = { "foo", "bar", "baz", "raz" };
		menu[0].items = &menu0[0];
	}

Now menu[0].items points to the first element of the four-element array
`menu0'.  Each element of that array has type `pointer to char', and
each points to a (read-only) `C string' (i.e., array of one or more
characters, ended by '\0') allocated somehow by the compiler.

If, instead, the `strings' are to be read from an external file, the
process is more complicated; I will not go into this here.
-- 
In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 7163)
Domain:	chris@cs.umd.edu	Path:	uunet!mimsy!chris

chris@mimsy.umd.edu (Chris Torek) (04/09/90)

In article <23571@mimsy.umd.edu> I wrote:
>...	char	**item; 	/* this may be wrong, but I want a arbitrary
>...	...item[0] = "Item0";
>This is a type mismatch.

Oops.  My brain was on fire.  Sorry about that.  The types do match; the
problem (as given by the rest of my article) is that the `item' pointer
has no valid value.
-- 
In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 7163)
Domain:	chris@cs.umd.edu	Path:	uunet!mimsy!chris