main() arguments, was Re: typedef-ing an array

Chris Torek chris at mimsy.umd.edu
Wed Jul 4 04:32:34 AEST 1990


In article <25247 at mimsy.umd.edu> I wrote:
>>[main()] must (yes must) be `int main', even if it never returns.
>>It may have either 0 arguments or two (int argc, char *argv).

In article <12433 at sun.udel.edu> toor at sun.udel.edu (Kartik S Subbarao)
writes:
>I beg to differ.  [void main(dumdum)] works fine with gcc, and ...
>works fine with plain 'ol cc.
>So you CAN have a) void main if you desire,
>		b) only one argument to main.

Your universe is too small.  If I said

	int main() { if (*(char *)1024 == 0) return 1; return 0; }

works fine with both gcc and cc (hint: it does in fact work fine with
both ... on SOME machines), would you say that you are allowed to read
location 1024?

In article <4238 at jato.Jpl.Nasa.Gov> kaleb at mars.jpl.nasa.gov (Kaleb Keithley)
writes:
>K&R 2nd Ed. states (p. 26):
>A function need not return a value. [...] Since main is a function like any
>other, it may return a value to its caller...
>
>Furthermore, on p. 164 (Ibid.) it is stated:
>Within main, return expr is equivalent to exit(expr).  exit has the
>advantage...

However, you might also note that it does NOT say that you may make
main a void function if main uses exit() rather than return.  There is
a reason for this.

>If exit() is used rather than return, I submit that declaring main as 
>returning type void is not only legal, but correct, as lint plus ANSI
>compilers will complain that there is no return statement.

They may indeed complain, but they will be incorrect in so doing.  The
ANSI C standard X3.159-1989 is very carefully designed to allow machines
to use a different call/return mechanism for functions that return values
versus functions that do not return values.  For instance, on a machine
with no registers, the code for a function `int f(x) { return x+1; }'
might be, e.g.,

		.export	f_
	f_:
		sub	#2,sp		| create local stack space
		| stack layout (2 byte `int's):
		|	4(sp)	arg 1
		|	2(sp)	pointer to return value location
		|	0(sp)	return pc
		|	-2(sp)	scratch space
		mov	4(sp),-2(sp)	| copy value of x
		add	#1,-2(sp)	| compute x+1
		mov	-2(sp), at 2(sp)	| `return' it
		add	#2,sp		| undo stack
		ret

Note what happens on this machine if we say

	extern void exit(int);
	extern int foo(int);
	int main(int argc, char **argv) {
		(void) foo(argc == 1 ? 0 : 1);
		exit(0);
	}

This compiles to, e.g.,

	main_:	.export	main_
		sub	#2,sp		| create stack space, as before
		|	6(sp)	argv
		|	4(sp)	argc
		|	2(sp)	place to store return value from main
		|	0(sp)	return address (C library startup code)
		|	-2(sp)	scratch space
		mov	4(sp),-2(sp)	| copy argc to temp space
		sub	#1,-2(sp)	| subtract 1
		jnz	-2(sp),L1	| branch if -2(sp)!=0, i.e., argc!=1
		mov	#0,-2(sp)	| argument to foo is 0
		jmp	L2		| merge
	L1:	mov	#1,-2(sp)	| argument to foo is 1
	L2:	sub	#4,sp		| foo has a return value
		mova	2(sp),0(sp)	| foo's return value will be stored in
					| the location we used for the argument
		call	foo_		| call foo()
		add	#4,sp		| fix stack
		mov	#0,-2(sp)	| argument to exit is 0
		sub	#2,sp		| exit has no return value
		call	exit_
		add	#2,sp		| (compiler thinks exit returns)
		ret			| ... without return a value.

Now watch what happens if we declare main() as void:

	void main(int argc, char **argv) { foo(argc == 1 ? 0 : 1); ... }

compiles to:

	main_:	.export	main_
		sub	#2,sp		| create stack space, as before
		|	4(sp)	argv
		|	2(sp)	argc
		|	0(sp)	return address (C library startup code)
		|	-2(sp)	scratch space
		mov	2(sp),-2(sp)	| copy argc to temp space
		sub	#1,-2(sp)	| subtract 1
		jnz	-2(sp),L1	| branch if -2(sp)!=0, i.e., argc!=1
		mov	#0,-2(sp)	| argument to foo is 0
		jmp	L2		| merge
	L1:	mov	#1,-2(sp)	| argument to foo is 1
	L2:	sub	#4,sp		| foo has a return value
		mova	2(sp),0(sp)	| foo's return value will be stored in
					| the location we used for the argument
		call	foo_		| call foo()
		add	#4,sp		| fix stack
		mov	#0,-2(sp)	| argument to exit is 0
		sub	#2,sp		| exit has no return value
		call	exit_
		add	#2,sp		| (compiler thinks exit returns)
		ret			| ... without return a value.

Unfortunately for us, the thing that calls main() with argc and argv
put a return value pointer into 2(sp), so that we called foo() with
the result of `return value pointer == 1 ? 0 : 1' rather than with
the result of `argc == 1 ? 0 : 1'.  If we try to examine the value of
argv, we find instead the value of argc.  Indeed, we could write our
main as

	void main(int *ret, int argc, char **argv) {
		foo(argc == 1 ? 0 : 1);
		exit(0);
	}

and it would work---even though it would ALSO work when written with
two arguments as `int main(int argc, char **argv)'.
		
>Second bone to pick is the assertion that main() has two arguments (???)
>Since when?  What about the third allowable argument; envp?

It is not allowed (see your .signature quote :-) ).  If you are trying
to write a portable program, you must not use this invisible third argument.

>I know that both UNIX and DOS (M'soft C compilers anyway) support
>char **envp ... as the third parameter to main.

If you are building a machine on which two-argument functions are very
different from three-argument functions (think along lines similar to the
weird machine I described above, where no-value functions are very
different from value-functions), and you want to support UNIX, you will
have to write a compiler that `knows' how main() really works, and
internally converts the (standard, portable, correct) main

	int main(int argc, char **argv) { ...

into object code that appropriately understands and ignores the invisible
third argument.

>"So that's what an invisible barrier looks like"

Indeed: compiler magic, or `all done with mirrors'.


There is an `exercise for the reader' hidden above as well.  Given
the sample code for main() above, can the compiler avoid using a
separate temporary for `ret' in

	int sum(int n, int *vec) {
		int i, ret = 0;
		for (i = 0; i < n; i++)
			ret += *vec++;
	}

by accumulating the return value in @2(sp)?  Why or why not?  If not,
how would you change the code for main so that it could?  Under what
conditions would so doing actually be advantageous?
-- 
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