[comp.lang.c] Why does lint complain about this?

ramey@m2.csc.ti.com (Joe Ramey) (04/24/89)

When I run lint on this program

main()
{
  try(0);
}

try(foo)
char *foo;
{
}

I get this output:

trylint.c:
trylint.c(7): warning: argument foo unused in function try
try, arg. 1 used inconsistently	trylint.c(8)  ::  trylint.c(3)

Why does lint say that the arg. is used inconsistently?  I thought
that zero could be assigned to any pointer type.  Shouldn't lint
recognize the constant 0 and realize that it is compatible with (char *) ?

Joe Ramey (ti-csl!ramey , ramey@csc.ti.com)
TI Computer Science Center

gwyn@smoke.BRL.MIL (Doug Gwyn) (04/25/89)

In article <75688@ti-csl.csc.ti.com> ramey@m2.csc.ti.com (Joe Ramey) writes:
>Why does lint say that the arg. is used inconsistently?  I thought
>that zero could be assigned to any pointer type.  Shouldn't lint
>recognize the constant 0 and realize that it is compatible with (char *) ?

"lint" is correct.  With no prototype in scope for the function (always
the case for pre-ANSI C), the only conversion applied to function arguments
is "default widening", e.g. char to int.  The integer constant 0 is passed
as an int, which indeed is not compatible with the parameter type later
declared for that function.

guy@auspex.auspex.com (Guy Harris) (04/26/89)

>Why does lint say that the arg. is used inconsistently?

Because it is.  You're passing an integer constant "0" to a routine
expecting a "char *".

>I thought that zero could be assigned to any pointer type.

It can, but that's because in an assignment the compiler knows that the
thing being assigned to is a pointer, and therefore that the "0" must be
converted to a null pointer constant of the appropriate pointer type. 
Unless your C compiler supports function prototypes, and unless you use
them, the compiler does not know that the "0" matches a formal parameter
of pointer type, and therefore cannot do the conversion; you have to
explicitly tell it to do so, by casting it to the appropriate pointer
type:

	try((char *)0);

(The compiler could, perhaps, in principle know in this particular case
that the formal argument is of pointer type; however, in practice,
compilers don't do that - I'm not sure whether pre-(d)pANS C
specifications permit it to do so, but the (d)pANS explicitly specifies
that the compiler should not do so.)

If you plan to compile this code only with compilers that support
function prototypes (and know for certain that your management isn't ever
going to come in the door and ask you to port it to a C implementation
that doesn't support them), you can (and should!) use them:

	void	try(char *foo);	/* "void", assuming it returns no value */

	/*
	 * As long as we're being type-correct...
	 */
	/*ARGSUSED*/
	int
	main(int argc, char **argv)
	{
		try(0);
		return 0;
	}

	void
	try(char *foo)
	{
		...
	}

in which case the compiler *will* recognize that the formal argument is
of type "char *" and will convert the "0" to a null pointer of type
"char *".

>Shouldn't lint recognize the constant 0 and realize that it is
>compatible with (char *) ?

Since the compiler won't do so, no, "lint" shouldn't do so; the code in
the form you gave it won't work on, say, machines where a null pointer
of type "char *" and an integer with the value 0 don't have the same bit
pattern (e.g., a system where an integer is 16 bits long and a "char *'
is 32 bits long), and if "lint" didn't warn you about this it would be
remiss in its duties.

geoff@cs.warwick.ac.uk (Geoff Rimmer) (04/26/89)

In article <75688@ti-csl.csc.ti.com> ramey@m2.csc.ti.com (Joe Ramey) writes:

> When I run lint on this program
> 
> main()
> {
>   try(0);
> }
> 
> try(foo)
> char *foo;
> {
> }
> 
> I get this output:
> 
> trylint.c:
> trylint.c(7): warning: argument foo unused in function try
> try, arg. 1 used inconsistently	trylint.c(8)  ::  trylint.c(3)
> 
> Why does lint say that the arg. is used inconsistently?  I thought
> that zero could be assigned to any pointer type.  Shouldn't lint
> recognize the constant 0 and realize that it is compatible with (char *) ?
> 

When lint sees 
	try (0);

it says "Hmm.  The 0 looks like an integer type to me."  So when it
comes to the definition of try() and sees the argument is now a 
"char *", it barfs.

If you want lint to know that the argument is actually a "char *", you
can do either of the following things:

(1) --> declare try() before main():

	try (foo) 
	    char *foo; 
	{}

	main() { try(0); }

(2) --> cast the 0 to (char *):

	main() 
	{ try ( ( char* ) 0); } 

	try(foo) 
	    char *foo;
	{
	}

(3) --> (the best solution) GET AN ANSI C COMPILER, such as gcc !!!
			    ^^^^^^^^^^^^^^^^^^^^^^

	This way, you would code your program thus:

	void try (char *foo);  /* function prototype tells gcc what the
				  argument types are */
	int main (void)        /* 'void' means no arguments */
	{
	    try(0);	       /* gcc already knows that the argument
				  is (char *) */
	}

	void try (char *foo)
	{
	}

How did people ever survive without function prototypes! :-)

> Joe Ramey (ti-csl!ramey , ramey@csc.ti.com)
> TI Computer Science Center

Geoff

        +----------------------------------------------------------+
        |       GEOFF RIMMER -                                     |
        |               FRIEND OF FAX BOOTHS AND ANSI C            |
        |       geoff@uk.ac.warwick.emerald                        |
        |       Computer Science, Warwick University, England.     |
        |       "Gimme a computer and I can do anything"  (*)      |
        +----------------------------------------------------------+

        (*) as long as UNIX, emacs and gcc are also provided :-)

mat@mole-end.UUCP (Mark A Terribile) (04/27/89)

In article <75688@ti-csl.csc.ti.com>, ramey@m2.csc.ti.com (Joe Ramey) writes:
>   try(0);
> ...
 
> try(foo)
> char *foo;
> {

> trylint.c:
> trylint.c(7): warning: argument foo unused in function try
> try, arg. 1 used inconsistently	trylint.c(8)  ::  trylint.c(3)
 
> Why does lint say that the arg. is used inconsistently?  I thought
> that zero could be assigned to any pointer type.  ...

The problem is that the compiler (or LINT) doesn't know that what must
actually be passed is a  char* .

Thus, the compiler could pass a 16-bit zero to a routine expecting a 32-bit
pointer.  This is why function prototypes (ANSI C, C++) are better than LINT.

(Oh, do I expect a debate on that last statement!)

You should write

	try( (char *) 0 );

in your sample code.
-- 

(This man's opinions are his own.)
From mole-end				Mark Terribile

kremer@cs.odu.edu (Lloyd Kremer) (04/27/89)

In article <1773@ubu.warwick.UUCP> geoff@cs.warwick.ac.uk (Geoff Rimmer) writes:
>How did people ever survive without function prototypes! :-)

They coded correctly, casting function arguments when necessary.

Prototypes are very helpful in error checking, but it is an unwise practice
to *depend* on the compiler to do your casting for you.  If a function call
would need an explicit cast to work correctly in the absence of a prototype,
then it should have an explicit cast.

One of the more common errors of this type (pun intended) occurs frequently
in UNIX exec calls:

	execl(path, arg0, arg1, arg2, 0);  /* non-portable */

instead of

	execl(path, arg0, arg1, arg2, (char *)0);

-- 
					Lloyd Kremer
					Brooks Financial Systems
					...!uunet!xanth!brooks!lloyd
					Have terminal...will hack!

geoff@cs.warwick.ac.uk (Geoff Rimmer) (04/29/89)

In article <8660@xanth.cs.odu.edu> kremer@cs.odu.edu (Lloyd Kremer) writes:

> In article <1773@ubu.warwick.UUCP> geoff@cs.warwick.ac.uk (Geoff Rimmer) writes:
> >How did people ever survive without function prototypes! :-)
> 
> They coded correctly, casting function arguments when necessary.
> 
> Prototypes are very helpful in error checking, but it is an unwise practice
> to *depend* on the compiler to do your casting for you.  If a function call
> would need an explicit cast to work correctly in the absence of a prototype,
> then it should have an explicit cast.

It was my understanding that confirming ANSI compilers will
automatically cast their arguments, if the function has been correctly
prototyped.  The only exception would be varadic functions, since a
prototype like

	int execl (char *, char *, ...);

does not tell the compiler what the types of the optional arguments
will be.  This means that your execl example:

> 	   execl(path, arg0, arg1, arg2, (char *)0);

does indeed require a cast even in ANSI C.

However, if I have a function that takes a char pointer, and I have
told the compiler so by means of a prototype, why can't I then call
the function with:

	func(0);    ( or func(NULL); if you prefer)

I'm not sure if you're saying this is wrong, or just bad *style*.
If ANSI compilers are supposed to cast arguments correctly, then I am
perfectly willing to trust my compiler to do so.  If I have omitted a
prototype, I want to be told so by the compiler.  That way I can't
lose (except for varadic as mentioned above) - or can I?

#include <std/cliche'>

> -- 
> 					   Lloyd Kremer
> 					   Brooks Financial Systems
> 					   ...!uunet!xanth!brooks!lloyd

Geoff

	/---------------------------------------------------------------\
	|	GEOFF RIMMER  - Friend of fax booths, ANSI C, PCBH,	|
	|			phone *numbers*	& MPFC & printf		|
	|	email	: geoff@uk.ac.warwick.emerald			|
	|	address : Computer Science Dept, Warwick University, 	|
	|		  Coventry, England.				|
	|	PHONE	: +44 203 692320 (10 lines) If I'm out please	|
	|			   leave a message with my secretary.	|
	|	FAX	: +44 865 726753				|
	\---------------------------------------------------------------/

guy@auspex.auspex.com (Guy Harris) (05/02/89)

 >Prototypes are very helpful in error checking, but it is an unwise practice
 >to *depend* on the compiler to do your casting for you.

Unless you're working with a compiler that supports prototypes, in which
case if the compiler won't do the conversions for you, it has what is
technically known as a "bug".

 >If a function call would need an explicit cast to work correctly in
 >the absence of a prototype, then it should have an explicit cast.

That's a stylistic matter - it doesn't *have* to have an explicit case
if your compiler supports prototypes, but if it makes you feel better to
explicitly cast the argument, go ahead and do so.

 >One of the more common errors of this type (pun intended) occurs frequently
 >in UNIX exec calls:
 >
 >	execl(path, arg0, arg1, arg2, 0);  /* non-portable */
 >
 >instead of
 >
 >	execl(path, arg0, arg1, arg2, (char *)0);

...which is not a good example, since "execl" is a function with a
variable number of arguments and, as such, cannot have the types of all
its arguments properly described by a prototype - you have to put in an
ellipsis.  As such, the compiler *cannot* (without extensions beyond the
scope of ANSI C) know that all arguments to "execl" should have type
"char *", and cannot convert a naked "0" to "(char *)0" in a call.

Another common error is

	setbuf(file, 0);	/* or NULL */

which the compiler *can* handle with prototypes, since the prototype for
"setbuf" is:

	void setbuf(FILE *stream, char *buf);

so it *does* know that the second argument should have type "char *".

The only problem I can see with trusting prototypes if you're never
going to use a compiler that doesn't support them is that you might not
have the proper prototype in scope.  This is a quality-of-implementation
issue: the compiler *should* have a mode in which it warns of legal but
questionable constructs such as calls to a function with no prototype in
scope, non-prototype definitions of functions, etc..  There should also
be a "lint" to check for problems that the compiler won't complain
about....

bet@dukeac.UUCP (Bennett Todd) (05/03/89)

In article <1794@ubu.warwick.UUCP> geoff@cs.warwick.ac.uk (Geoff Rimmer) writes:
>[...]
>It was my understanding that confirming ANSI compilers will
>automatically cast their arguments, if the function has been correctly
>prototyped.
>[...]
>If ANSI compilers are supposed to cast arguments correctly, then I am
>perfectly willing to trust my compiler to do so.  If I have omitted a
>prototype, I want to be told so by the compiler.

I *think* that gcc with the -Wall option should give warnings for any
functions called without a prototype in scope; I always compile with gcc using
-Wall. I had a large package, containing zillions of functions organized into
multiple libraries. I had the library Makefiles generate the library's foo.h
file automatically from a template and a sed script which extracted function
prototypes from function declarations; I had "make install" install the proper
header file in /usr/local/include; I thought I was safe.

I called a function which took float parameters, using integer variables for
the arguments. I said to myself (very cleverly) "the compiler will generate
the cast for me, since I have the proper prototype in scope". KaBOOM! It
didn't work. On single-stepping through I found that where I was passing in
128, the function was receiving some ridiculous humongous number. Chagrined, I
put in the cast (Rule #1: ALWAYS say exactly what you mean. ALWAYS.). The
problem went away. I don't know whether something bizarre happened in my
convoluted generation procedure, or whether I tickled some extremely obscure
bug in gcc; frankly I don't care. I consider the lesson clear. If the compiler
gets it wrong with an explicit cast, then you've got a fatally broken
compiler.  If what needs to happen can be sufficiently unclear and implicit
that humans and/or computers can get confused, then maybe it's best to avoid
it? 

-Bennett
bet@orion.mc.duke.edu