[comp.lang.c] Misdeclaring "main"

guy@auspex.auspex.com (Guy Harris) (07/22/89)

>Sounds like SGI has an unusual argument passing method.

Nope.  It turned out the problem was that he'd written something like:

	struct foobar {
		...
	} <<<<<<<<<<<<<NOTE: missing semicolon!

	main(argc, argv)
		int argc;
		char *argv[];
	{
		...

The missing semicolon caused the "struct" declaration to get glued to
the definition of "main", so that "main" was being defined as returning
a "struct foobar".  This presumably changed the calling sequence of
"main" in such a way as to scramble the incoming arguments.

I've seen this error crop up before; it's certainly easy enough to make.
("For want of a semicolon, the program was lost....")

I like to explicitly declare the return type of *all* functions, even
those returning "int", and at least in ANSI C (and in most UNIX C
implementation) "main" does, in fact, return "int", so I declare it as
such.  I just realized that doing so can also catch this particular
mistake, since, while

	struct foobar {
		...
	}

	main(argc, argv)
	...

is legal (yes, really),

	struct foobar {
		...
	}

	int
	main(argc, argv)
	...

isn't.  So, always declare "main" as returning "int"; it may catch that
error someday....

gwollman@tnl.UUCP (Garrett A. Wollman) (07/23/89)

In article <2268@auspex.auspex.com>, guy@auspex.auspex.com (Guy Harris) writes:
> Nope.  It turned out the problem was that he'd written something like:
> 
> 	struct foobar {
> 		...
> 	} <<<<<<<<<<<<<NOTE: missing semicolon!
> 
> 	main(argc, argv)
> 		int argc;
> 		char *argv[];
> 	{
> 		...
> 
> The missing semicolon caused the "struct" declaration to get glued to
> the definition of "main"...  This presumably changed the calling sequence of
> "main" in such a way as to scramble the incoming arguments.
> [. . .]

It is, of course, obvious, that if we used ANS C-compatible function
prototypes, this sort of problem would never get past the compiling
stage.  For instance, if you had:


int main(int,char **);

struct foo {
	int i,j;
	double k;
}

main( /* and so on */


The cmompiler would pick up the mismatch.  Of course, if you're using
prototypes, then your compiler also supports ~voids~ and stuff like
that, so you (if you were a careful coder) wouldn't even fall back on
this, but get a syntax error right away.

I use the type-mismatch trick to help me bring archaic forms up to ANS. 
I can look through the file and create prototypes for each function,
which I put at the beginning of the file.  Then, I let the compiler
bring me to all the type mismatches, which will be the points where a
void function was not declared as void.  Saves a lot of time.

Actually, the whole business of prototyping saves me a considerable
amount of debugging time.  This allows me to catch errors such as
passing a char to a function which takes an int, when the char was
supposed to be unsigned.  (Don't think this is a problem?  Try writing
to a file, using fputc('\xff',fp).  The '\xff' will be sign-extended to
-1.)

-GAWollman

-- 
"(-::-)"    (Siamese twins)   | "This is a public-access system, so I don't 
gwollman@tnl.UUCP             |  know what the operator's opinions are."
             ...uunet!uvm-gen!tnl!gwollman

gwyn@smoke.BRL.MIL (Doug Gwyn) (07/23/89)

In article <220@tnl.UUCP> gwollman@tnl.UUCP (Garrett A. Wollman) writes:
-Actually, the whole business of prototyping saves me a considerable
-amount of debugging time.  This allows me to catch errors such as
-passing a char to a function which takes an int, when the char was
-supposed to be unsigned.  (Don't think this is a problem?  Try writing
-to a file, using fputc('\xff',fp).  The '\xff' will be sign-extended to
--1.)

I don't understand your example: '\xff' is an int, fputc() takes an int.
How is the use of prototypes going to help here?  Indeed, why is your
compiler compiling '\xff' as -1?

scs@adam.pika.mit.edu (Steve Summit) (07/29/89)

In article <2268@auspex.auspex.com>, Guy Harris writes:
> Nope.  It turned out the problem was that he'd written something like:
> 
> 	struct foobar {
> 		...
> 	} <<<<<<<<<<<<<NOTE: missing semicolon!
> 
> 	main(argc, argv)
> 		int argc;
> 		char *argv[];
> 	{
> 		...
> 
> The missing semicolon caused the "struct" declaration to get glued to
> the definition of "main"...  This presumably changed the calling sequence of
> "main" in such a way as to scramble the incoming arguments.

It occurred to me, the last time or so ago that this problem was
discussed, that the compiler could detect it fairly easily, by
disparaging the productions

	struct { struct-decl-list } id (arg-list) decl-list compound-statement
and
	struct tag { struct-decl-list } id (arg-list) ...

in favor of

	struct tag id (arg-list) decl-list compound-statement

for declaring functions returning structures.  That is, the
preferred way to declare a function returning a structure would
be (and is!) with a structure tag, referring to a previously-
declared structure.  An attempt to declare a function within the
same declaration in which the shape of the structure was being
described would elicit an warning.

Implementation of such a feature would be straightforward: in the
structure describing a type, a bit could be kept for structure
types, recording whether the type had arisen from an explicit
structure definition, or implicitly through a structure tag.
(This bit would likely have to be kept one level of indirection
above the lowest-level structure-describing structure, since
type-equivalent structures are often stored exactly once, with
equivalence detected by pointer equality.)  A function
declaration with a return type of "explicit structure" would
trigger the message.

Obviously, this warning would be appropriate for all functions,
not just those named "main."  (This error is common because our
fingers aren't used to typing semicolons after close-squiggly-braces.)

If you think about it, the compiler warning I've suggested could
hardly arise accidentally (i.e. from intentionally-written code).
The production

	struct { struct-decl-list } id (arg-list) decl-list compound-statement

yields a function that cannot be (correctly) called, since no
other object can be declared with a type-equivalent structure.
The production

	struct tag { struct-decl-list } id (arg-list) ...

yields a function that can only be called by routines appearing
later in the same file.

                                            Steve Summit
                                            scs@adam.pika.mit.edu

mcdaniel@uicsrd.csrd.uiuc.edu (Tim McDaniel) (07/29/89)

This is cross-posted to comp.lang.c (original article's newsgroup) and
comp.std.c (I have standards questions).  Followups have been
redirected to comp.std.c.

In article <13104@bloom-beacon.MIT.EDU> scs@adam.pika.mit.edu (Steve
Summit) writes:
>The production
>
>	struct { struct-decl-list } id (arg-list) decl-list compound-statement
>
>yields a function that cannot be (correctly) called, since no
>other object can be declared with a type-equivalent structure.

I'm not so sure.  Separate compilation is allowed in C, but struct
tags (and their definitions) are not global in scope; for external
linkage, only the types and order of the struct's members matter.
I would think the following declarations are strictly conforming:

in foo.c:
        extern struct { int f1; float f2; } func(); ...

in bar.c:
        struct { int a6; float whacky; } func() ...

However, K&R 2, 1st edition, p. 213 says

     A structure or union specifier with a list but no tag creates a
     unique type; it can referred to directly only in the declaration
     of which it is a part.

>The production
>
>	struct tag { struct-decl-list } id (arg-list) ...
>
>yields a function that can only be called by routines appearing
>later in the same file.

This statement is even more problematical.  How does it differ from
	struct tag { struct-decl-list };
        struct tag id (arg-list) ...
?  If there is no difference, the "struct tag" declaration could just
as easily be in an include file, and another file could include it and
declare
        extern struct tag id ();
Surely *this* behavior is blessed by the standard.

--
"Let me control a planet's oxygen supply, and I don't care who makes
the laws." - GREAT CTHUHLU'S STARRY WISDOM BAND (via Roger Leroux)
 __
   \         Tim, the Bizarre and Oddly-Dressed Enchanter
    \               mcdaniel@uicsrd.csrd.uiuc.edu
    /\       mcdaniel%uicsrd@{uxc.cso.uiuc.edu,uiuc.csnet}
  _/  \_     {uunet,convex,pur-ee}!uiucuxc!uicsrd!mcdaniel

scs@adam.pika.mit.edu (Steve Summit) (08/01/89)

In article <1612@garcon.cso.uiuc.edu> mcdaniel@uicsrd.csrd.uiuc.edu (Tim McDaniel) writes:
>This is cross-posted to comp.lang.c (original article's newsgroup) and
>comp.std.c (I have standards questions).  Followups have been
>redirected to comp.std.c.

Pooh.  Now I have to be more careful than I can be, without a
copy of the draft at hand...

>In article <13104@bloom-beacon.MIT.EDU> scs@adam.pika.mit.edu (Steve
>Summit) writes:
>>The production
>>	struct { struct-decl-list } id (arg-list) decl-list compound-statement
>>yields a function that cannot be (correctly) called, since no
>>other object can be declared with a type-equivalent structure.
>
>I'm not so sure.  Separate compilation is allowed in C, but struct
>tags (and their definitions) are not global in scope; for external
>linkage, only the types and order of the struct's members matter.

It has been suggested that permission to base struct equivalence
across files on types and order, rather than the stricter means
required within one file, is an exception, reflecting the
realities of conventional implementations of separate compilation.
The way I think about it is that this is perfectly legal:

	s.h:	struct s {int i;};

	a.c:	#include "s.h"		b.c:	#include "s.h"
		struct s s;			extern struct s s;

while this is suspect:

	a.c:	struct s {int i;};	b.c:	struct s {int i;};
		struct s s;			extern struct s s;

Obviously, a conventional compiler cannot tell these two cases
apart, which is why the exception allows the two struct s's in
the second example to be considered equivalent.  I would not mind
it if a smart compiler (or, more preferably, lint) complained
about the second form.

I have no idea what the chapter and verse of the pANS says on
this issue (that's why I'd have kept this in comp.lang.c...).
I'm describing a conceptual ideal, based on the obvious stylistic
requirement of describing the structure in exactly one place.  If
the pANS is less stringent (if its permission to base inter-file
struct equivalence on type and order is not an exception, as I
have suggested, but rather explicit and genuine) then I have no
complaint with it, and you don't need to correct me here.  (The
standard tries to, and should, be pragmatic.)  I hope we agree
why the first sample code above is vastly preferable to the
second.

>>The production
>>	struct tag { struct-decl-list } id (arg-list) ...
>>yields a function that can only be called by routines appearing
>>later in the same file.
>
>This statement is even more problematical.  How does it differ from
>	struct tag { struct-decl-list };
>        struct tag id (arg-list) ...
>?  If there is no difference, the "struct tag" declaration could just
>as easily be in an include file, and another file could include it...

Of course.  The difference between

	struct tag { struct-decl-list } id (arg-list) ...
and
	struct tag { struct-decl-list };
	...
	struct tag id (arg-list) ...

is precisely, and only, that the second form affords more
possibilities for equivalent declarations of type "struct tag,"
especially if the tag is defined in a header file.  That is
exactly what I was trying to show.

I listed three productions:

 1.	struct { struct-decl-list } id (arg-list) decl-list compound-statement
 2.	struct tag { struct-decl-list } id (arg-list) ...
 3.	struct tag id (arg-list) ...

and suggested that the first one was, in practice, useless; the
second one (theoretically) barely useful; and the third one the
only one that would ever appear in real and/or well-written
programs.

Between the lines is a question, which I'll make explicit: if, in
an attempt to catch the common mistake of misdeclaring main (or
another function) by leaving out a semicolon after a preceding
structure declaration, a compiler were to issue warnings for
productions 1. and 2. above, would these warnings be elicited
inappropriately for anyone's real programs?  (No contrived
examples or obfuscated C contest entries, of course.)

                                            Steve Summit
                                            scs@adam.pika.mit.edu