structure function returns -- how?

Chris Torek chris at mimsy.UUCP
Mon Dec 8 13:29:23 AEST 1986


In article <131 at hcx1.UUCP> notes at hcx1.UUCP (Dave Ray) writes:
>The pcc compilers I have seen apparently "set" [a return value] in
>[the staack frame of the function that returns a structure]. 
>They then return a pointer to [this stack object], and copying from
>that pointer is done [by the caller].  But what happens if a signal
>comes through during the copy?

The return value gets clobbered.

The 4BSD Vax PCC allocates a static return value in a function that
returns a structure, and returns a pointer to the static return
value.  I.e., given

	struct foo
	f()
	{
		...
		return (rv);
	}

	g()
	{
		struct foo t;

		t = f();
	}

the compiler in fact generates code more like this:

	struct foo *
	f()
	{
		static struct foo _temp_;
		...
		_temp_ = rv;
		return (&_temp_);
	}

	g()
	{
		struct foo t;

		t = *f();
	}

>Another compiler overcomes this problem by passing the address of a 
>local structure in [the caller]'s frame to the [structure valued] function.

That works too.

>Are there other issues to be considered?

PCC's method is not re-entrant, but averts disaster when people
call a structure-valued function just for side effects, and neglect
to declare the function first.  If I were writing the compiler, I
would use the last method: the caller should pass a pointer to an
object to be set to the return value.  This is in particular more
efficient in the f() and g() example above, since g() can pass a
pointer to `t' and avoid a copy.  One possible problem with this
is in code of this form:

	struct foo f() { ... }

	g()
	{
		static struct foo t;

		t = f();
	}

Since `t' is static here, it is conceivable that there might be
problems with non-atomic adjustment of `t' in an optimised version
of f(), if f() calls g() recursively.  To give a more concrete
example (vertically compressed for small screens):

	typedef struct point { int x, y; } point;

	point f() {
		struct point rv;
		rv.y = global_y++;
		rv.x = more() ? g() : 0;
		return (rv);
	}

	int g() {
		static struct point last_point = { 1, 1 };
		if (more())
			return (last_point.y + 1);
		last_point = f();
		return (last_point.y);
	}

An `optimising' compiler might compile instead this:

	void f(rvp) point *rvp; {
		rvp->y = global_y++;
		rvp->x = more() ? g() : 0;
	}

	int g() {
		static struct point last_point = { 1, 1 };
		if (more())
			return (last_point.y + 1);
		f(&last_point);
		return (last_point.y);
	}

But this will not always have the same effect, since f() could
alter g's last_point during, rather than after, the recursion,
making g() return a different value in the recursive call.  A
`correct' optimisation of f():

	void f(rvp) point *rvp; {
		int _temp_y = global_y++;
		rvp->x = more() ? g() : 0;
		rvp->y = _temp_y;
	}

That is, all the assignments must be done at the end of the function
in the presence of recursion.  Asynchronous calls (signals) are
even worse, but create atomicity problems with any scheme.
-- 
In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 7690)
UUCP:	seismo!mimsy!chris	ARPA/CSNet:	chris at mimsy.umd.edu



More information about the Comp.lang.c mailing list