[net.lang.c] What is wrong with this program?

bruce@graffiti.UUCP (Bruce Jilek) (08/08/85)

Why does printf insist that data->ut_line is a null string while
putchar can display all of the characters of this array?

Sorry if this is a trivial question, but it beats the hell 
out of me.  (If it helps, this is on a Fortune 32:16 - sort of v7.)


/****************************************************************
    This program is a rough imitation of "who /usr/adm/wtmp".
****************************************************************/
#include <stdio.h>

main()
{

	struct utmp {    /* This structure is straight out of utmp.h */
		char	ut_line[8];		/* tty name */
		char	ut_name[8];		/* user id */
		long	ut_time;		/* time on */
	};

	FILE *f;
	int i;
	struct utmp *data;
	if (f = fopen ("/usr/adm/wtmp", "r")) {
		while ((fread (data, sizeof(struct utmp), 1, f))  >  0) {
			putchar('\t');
			for (i = 0; i <= 7; i++) {
				putchar(data->ut_line[i]);
			}
			printf("\n");
			printf("%s	%s	%ld\n", data->ut_name,
				data->ut_line, data->ut_time);
		}
		fclose(f);
	}
}

/**********************************************************************
  End of program
**********************************************************************/

Sample output:
	
	tty03                 /*  This shows that data->ut_line isn't null */
bruce	(null)	492299400     /*  So what's the problem in this line?      */
	tty03
	(null)	492299405
	tty02
mike	(null)	492299409
        .
        .
        .

chris@umcp-cs.UUCP (Chris Torek) (08/09/85)

One question: to what does "data" point?  You haven't ever *set* it
to anything....

(Probably, "data" happens to point to a region on the stack, or in
the data segment, that is clobbered by the call to printf(), but
not by the macro invoked by putchar().)

Lint would have caught that error, by the way.
-- 
In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 4251)
UUCP:	seismo!umcp-cs!chris
CSNet:	chris@umcp-cs		ARPA:	chris@maryland

bc@cyb-eng.UUCP (Bill Crews) (08/10/85)

> Why does printf insist that data->ut_line is a null string while
> putchar can display all of the characters of this array?

Well, for one thing, the variable data is never initialized.  The fread is
surely clobbering things!

-- 

  /  \    Bill Crews
 ( bc )   Cyb Systems, Inc
  \__/    Austin, Texas

[ gatech | ihnp4 | nbires | seismo | ucb-vax ] ! ut-sally ! cyb-eng ! bc

ark@alice.UUCP (Andrew Koenig) (08/10/85)

> #include <stdio.h>

> main()
> {

>	struct utmp {    /* This structure is straight out of utmp.h */
>		char	ut_line[8];		/* tty name */
>		char	ut_name[8];		/* user id */
>		long	ut_time;		/* time on */
>	};

>	FILE *f;
>	int i;
>	struct utmp *data;
>	if (f = fopen ("/usr/adm/wtmp", "r")) {
>		while ((fread (data, sizeof(struct utmp), 1, f))  >  0) {
>			putchar('\t');
>			for (i = 0; i <= 7; i++) {
>				putchar(data->ut_line[i]);
>			}
>			printf("\n");
>			printf("%s	%s	%ld\n", data->ut_name,
>				data->ut_line, data->ut_time);
>		}
>		fclose(f);
>	}
> }

When you are reading into the thing pointed to by data,
data doesn't point anywhere because you've never initialized it!
Change the declaration of data:

	struct utmp data;

and every place you now use data, use its address instead:

	while ((fread (&data, sizeof(struct utmp), 1, f)) > 0) {
		...
			putchar (data.ut_line[i]);
		...

		printf ("%.8s      %.8s      %ld\n", data.ut_name,
			data.ut_line, data.ut_time);
		...

Also notice the %.8s -- this defends against the field being
completely full.  In that case it would not have a null terminator.

andrew@grkermi.UUCP (Andrew W. Rogers) (08/10/85)

In article <117@graffiti.UUCP> bruce@graffiti.UUCP (Bruce Jilek) writes:
>Why does printf insist that data->ut_line is a null string while
>putchar can display all of the characters of this array?
>
>		char	ut_line[8];		/* tty name */
>	...
>			for (i = 0; i <= 7; i++) {
>				putchar(data->ut_line[i]);
>			printf("\n");
>			printf("%s	%s	%ld\n", data->ut_name,
>				data->ut_line, data->ut_time);
>	...
>
>Sample output:
>	
>	tty03                 /*  This shows that data->ut_line isn't null */
>bruce	(null)	492299400     /*  So what's the problem in this line?      */

Are you *sure* it really isn't null?  If the first character were \0, your
'putchar' loop would give no indication of that fact; the \0 would be
invisible and the remaining characters would print normally.  Of course,
'printf' would consider the string null.

Try replacing the 'putchar...' with 'printf("\\%03o ", data->ut_line[i]);'
and see what the characters in the string *really* are.  Either that, or
redirect the output to a file and get a binary/octal/hex dump of it.

AWR

levy@ttrdc.UUCP (Daniel R. Levy) (08/10/85)

bruce@graffiti.UUCP (Bruce Jilek) <117@graffiti.UUCP>:
>
>Why does printf insist that data->ut_line is a null string while
>putchar can display all of the characters of this array?
>...
>	struct utmp {    /* This structure is straight out of utmp.h */
>		char	ut_line[8];		/* tty name */
>		char	ut_name[8];		/* user id */
>		long	ut_time;		/* time on */
>	};
>...
>			for (i = 0; i <= 7; i++) {
>				putchar(data->ut_line[i]);
>			}
>			printf("\n");
>			printf("%s	%s	%ld\n", data->ut_name,
>				data->ut_line, data->ut_time);
>...

printf expects strings which are null-terminated at the end.  There is no
guarantee that what you get in the arrays in struct utmp will be this way.
Ergo, strange results.  Putchar is the way to go unless you want to copy
the data out of the struct utmp into something which is null terminated for
the sake of printf.
-- 
 -------------------------------    Disclaimer:  The views contained herein are
|       dan levy | yvel nad      |  my own and are not at all those of my em-
|         an engihacker @        |  ployer, my pets, my plants, my boss, or the
| at&t computer systems division |  s.a. of any computer upon which I may hack.
|        skokie, illinois        |
|          "go for it"           |  Path: ..!ihnp4!ttrdc!levy
 --------------------------------     or: ..!ihnp4!iheds!ttbcad!levy

guy@sun.uucp (Guy Harris) (08/11/85)

> >
> >Sample output:
> >	
> >     	tty03                 /*  This shows that data->ut_line isn't null */
> >bruce	(null)	492299400     /*  So what's the problem in this line?      */

> Are you *sure* it really isn't null?

Yes, he's sure; "printf" prints "(null)" if the pointer it's handed is
itself null, not if it points to a null string.  If the code posted was the
entire program, and not just a fragment, the problem is, as Chris Torek
pointed out, that the guy declared a pointer to the "utmp" structure, and
used it, without making it point to anything.  He should just have declared
a structure and used it directly.

Confusion about structures and pointers to them seems to be common.

	struct foo {
		...
	} bar;
	struct foo *bletch = &bar;

	<code using "bletch">

is not necessarily better than

	struct foo {
		...
	} bar;

	<code using "bar" directly>

and is more obscure (since the pointer isn't necessary, and the person
reading the code may waste time figuring out that fact).  It may have some
benefit if 1) "bletch" is in a register, 2) you have addressing modes like
"short_displacement(Rn)", and 3) you *don't* have addressing modes like
"short_displacement(SP)" or "short_displacement(FP)" or the displacement of
the members of the structure from the SP or FP isn't "short".  However, this
is uncommon (the PDP-11, VAX, M68000, Z8000 and CCI Power 6 families use a
general register as FP/SP, so it makes no difference; the NS32xxx family
supports the use of "displacement(FP)" in the same way it supports
"displacement(Rn)"; I suspect the WE32xxx family, and most of the other
machines out there do one or the other also; the 80*86 family looks like it
can with the proper choice of register to implement FP), and I'd be
surprised if it made enough difference to make the obscure code (which isn't
likely to run any faster on the machines mentioned above) worth it.
Nevertheless, I've seen this kind of thing often enough that I suspect it's
due to misunderstanding of C.

I've also seen code like that in the original article, where the user seems
to be under the impression that declaring a pointer automatically declares
something for it to point to *and* sets it to point to that - I've seen it
less, though, because it simply doesn't work.

	Guy Harris

roger@ll-sst (Roger Hale) (08/12/85)

> In article <117@graffiti.UUCP> bruce@graffiti.UUCP (Bruce Jilek) writes:
> >Why does printf insist that data->ut_line is a null string while
> >putchar can display all of the characters of this array?
> > ...
[ > main() {
  >	struct { char	ut_line[8]; ... } *data; ...	/* in effect */
] > }
> >
> >Sample output:
> >	
> >	tty03                 /*  This shows that data->ut_line isn't null */
> >bruce	(null)	492299400     /*  So what's the problem in this line?      */
> 
> Are you *sure* it really isn't null?  If the first character were \0, your
> 'putchar' loop would give no indication of that fact; the \0 would be
> invisible and the remaining characters would print normally.  Of course,
> 'printf' would consider the string null.
> ...
> AWR

[I tried sending to graffiti!bruce@Berkeley.ARPA, but no luck.]

In fact data->ut_line is a null \emphasis{pointer}, since printf is recognizing
a null string pointer for %s and printing '(null)' instead.  This check is in
doprnt.s in our V7 PDP11 system, though not in 4.2bsd's.

Data->ut_line is null because data is null and ut_line is at offset zero;
data is null by accident because this is your first time at this stack level
and the stack happens to start out zero filled.

If any of this were different it would have printed 'tty03' instead, leaving
no clue that *data had been trashed.  All this is Murphy-dependent; be glad
the symptoms were so clear!

Your servant,
Roger Hale  <roger@ll-sst.arpa>
	    (sorry, no usenet address)

peter@utah-gr.UUCP (Peter S. Ford) (08/13/85)

The declaration of 
f()
{
>	struct foo { 	...
>	} bar;
>	struct foo *bletch = &bar;
	...
}
is very useful when the structure needs to be passed around as an argument to
functions, and the life of the storage is for the length of the 
current procedure invocation.  This does reveal my preference for passing a 
variable which is a pointer to the structure "qqsv(bletch);" rather than
 "qqsv(&bar);". 

hosking@convexs.UUCP (08/15/85)

Among other things, it looks like you never allocate space for "data" in
your program.  You have a pointer, but it doesn't point to anything.  On
a Convex C-1, this leads to a core dump when "fread" tries to do a "bcopy"
with a null pointer.... which is invalid as a user address.  On VAXen and
other machines, null pointers are valid user addresses, which can make
finding such problems a lot more difficult.  One of the biggest problems
in porting 4.2 to a Convex C-1 was exactly this problem... programs tried
to use pointers which had never been initialized.

Hint:  Learn about the program "lint" and use it regularly.  It might have
saved you a lot of work.
				Doug Hosking
				Convex Computer Corp.
				Richardson, TX

anton@ucbvax.ARPA (Jeff Anton) (08/16/85)

In article <353@ttrdc.UUCP> levy@ttrdc.UUCP (Daniel R. Levy) writes:
>bruce@graffiti.UUCP (Bruce Jilek) <117@graffiti.UUCP>:
>>
>>Why does printf insist that data->ut_line is a null string while
>>putchar can display all of the characters of this array?
>>...
>>	struct utmp {    /* This structure is straight out of utmp.h */
>>		char	ut_line[8];		/* tty name */
>>		char	ut_name[8];		/* user id */
>>		long	ut_time;		/* time on */
>>	};
>>...
>>			for (i = 0; i <= 7; i++) {
>>				putchar(data->ut_line[i]);
>>			}
>>			printf("\n");
>>			printf("%s	%s	%ld\n", data->ut_name,
>>				data->ut_line, data->ut_time);
/* how about */		printf("%.8s\t%.8s\t%ld\n", ....
>>...
>
>printf expects strings which are null-terminated at the end.  There is no
>guarantee that what you get in the arrays in struct utmp will be this way.
>Ergo, strange results.  Putchar is the way to go unless you want to copy
>the data out of the struct utmp into something which is null terminated for
>the sake of printf.
	Not really, you are forgetting that the format "%.8s" will
cause printf to print up to a null or 8 chars max.  Unfortunately
this style of printf can not be used with sizeof for compile time
format changes without run time code support.
-- 
C knows no bounds.
					Jeff Anton
					U.C.Berkeley
					ucbvax!anton
					anton@berkeley.ARPA

guy@sun.uucp (Guy Harris) (08/17/85)

> 	Not really, you are forgetting that the format "%.8s" will
> cause printf to print up to a null or 8 chars max.  Unfortunately
> this style of printf can not be used with sizeof for compile time
> format changes without run time code support.

True, you can't have the compiler generate the "printf" string, but you can
do

	printf("%.*s", sizeof(thing), thing);

since you can use "*" for a field width or precision and pick up the value
from the argument list.

	Guy Harris

chris@umcp-cs.UUCP (Chris Torek) (08/18/85)

>... you are forgetting that the format "%.8s" will cause printf to
>print up to a null or 8 chars max.  Unfortunately this style of
>printf can not be used with sizeof for compile time format changes
>without run time code support.

Well, you can always use:

	printf("%.*s", sizeof (utmp.ut_name), utmp.ut_name);

or if you want exactly sizeof (utmp.ut_name) characters:

	#define NMAX (sizeof (utmp.ut_name))

	printf("%-*.*s", NMAX, NMAX, utmp.ut_name);

(The "-" gives "left adjustment"---blank filling at the right.)
-- 
In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 4251)
UUCP:	seismo!umcp-cs!chris
CSNet:	chris@umcp-cs		ARPA:	chris@maryland

guy@sun.uucp (Guy Harris) (08/20/85)

> You have a pointer, but it doesn't point to anything.  On
> a Convex C-1, this leads to a core dump when "fread" tries to do a "bcopy"
> with a null pointer.... which is invalid as a user address.  On VAXen and
> other machines, null pointers are valid user addresses, which can make
> finding such problems a lot more difficult.

On several other machines, null pointers are not valid user addresses; Suns
and CCI Power 5/20s come to mind (not surprisingly).  On VAXes running VMS,
null pointers are not valid user addresses; on VAXes running System V
Release 2 Version 2, or 4.2BSD with John Bruner's changes, there's a loader
option to build executable images where null pointers aren't valid.  Use
these options - they can catch problems before you have to move your code to
a machine where you have no alternative (besides, the chances are
non-negligible that your code has a problem even if you can dereference a
null pointer).

> Hint:  Learn about the program "lint" and use it regularly.  It might have
> saved you a lot of work.

Amen.  MANY program bugs (even ones in the kernel - I can testify to this)
can be caught simply by using "lint".  There may be some things that "you
just can't get lint to shut up about", but better too many complaints than
too few...

	Guy Harris