[comp.unix.wizards] reentrant vs. non-reentrant code

tedj@hpcilzb.HP.COM (Ted Johnson) (07/29/88)

Can someone please explain the difference between "reentrant" and
"non-reentrant" code?  Thanks!

-Ted

roy@phri.UUCP (Roy Smith) (07/31/88)

In article <1670001@hpcilzb.HP.COM> tedj@hpcilzb.HP.COM (Ted Johnson) writes:
> Can someone please explain the difference between "reentrant" and
> "non-reentrant" code?  Thanks!

	Reentrant code is, quite simply, code you can reenter without ill
effect.  Usually this means that each invocation of the code gets its own
set of variables, although this is not a sufficient condition to make
something reentrant.  Take a typical example in C, a recursve factorial
routine:

	fact (i)
	int i;
	{
		return (i<2 ? i : i*fact(i-1));
	}

	If you call fact(5), the first instance of i is 5.  Fact calls
itself with an argument of i=4.  Now you have two instances of fact in the
process of being executed.  This works because i is automatic; each
invocation of fact gets a new piece of memory in which to store i.

	Now, change the routine to look like:

	fact (i)
	int i;
	{
		static j;

		j = i;
		return (j<2 ? j : j*fact(j-1));
	}

and you end up with a bit of non-reentrant code: each invocation of fact
still has its own instance of i, but they all share the same j.  The second
time fact gets called, it overwrites the value of j still being used.
Actually, what I've just shown is a bit of code which is not recursively
reentrant, which is not quite the same thing as not being reentrant.  A
better example would be something like:

	sayfoo()
	{
		static char *str = "foo\n";

		while (*str)
		{
			putchar (*str);
			str++;
		}
	}

	The first time you call sayfoo(), it prints "foo\n", but each other
time you call it, it just returns without printing anything.  Any code with
static variables in it is likely to be non-reentrant.  This generally means
Fortran code is non-reentrant since Fortran uses static storage.  Self
modifying code is also almost certainly non-reentrant.  Sometimes code is
reentrant except for certain windows of vulnerability which much be locked
around.  A typical example is a Unix device driver.
-- 
Roy Smith, System Administrator
Public Health Research Institute
{allegra,philabs,cmcl2,rutgers}!phri!roy -or- phri!roy@uunet.uu.net
"The connector is the network"

anton@postgres.uucp (Jeff Anton) (08/01/88)

In article <3409@phri.UUCP> roy@phri.UUCP (Roy Smith) writes:
>In article <1670001@hpcilzb.HP.COM> tedj@hpcilzb.HP.COM (Ted Johnson) writes:
>> Can someone please explain the difference between "reentrant" and
>> "non-reentrant" code?  Thanks!
>
>	Reentrant code is, quite simply, code you can reenter without ill
>effect.  Usually this means that each invocation of the code gets its own
>set of variables, although this is not a sufficient condition to make
>something reentrant.  Take a typical example in C, a recursve factorial
>routine:
>

Reentrantcy is generally not a problem for most code since most code
deals with one thread of execution.  However, you might run against the
problem if you have a signal handler which uses code which you also use
durring normal execution.

Bewarned, recursive is not reentrant.  Code can be recursive without being
reentrant.  Since recursion is part of the design of a code sample, that
code can be written to do recursion only when the environment of the code
is ready.  You could, for example, have a static variable in a recursive
routine so long as you no longer use that variable after you make the
recursive call, or only use it after the last recursive call.

Also, you can have reentrant code without being recursive.  This is very
common and most C code is reentrant.  Recursion is part of the design of
code.  For example:

/* recursive and reentrant */
unsigned
fact1(i)
unsigned i;
{
	if (i < 3)
		return(i);
	return (i*fact1(i-1));
}

/* reentrant */
unsigned
fact2(i)
unsigned i;
{
	unsigned v;

	if (i < 3)
		return(i);
	v = 1;
	while (i > 1)
		v *= i--;
	return(v);
}

/* recursive - kids, don't try this at home */
unsigned
fact3(i)
unsigned i;
{
	static	unsigned	v;

	if (i < 3)
		return(v = i);
	fact3(i-1);
	return(v *= i);
}

/* badness */
unsigned
fact4(i)
unsigned i;
{
	static	unsigned v;

	if (i < 3)
		return(i);
	v = 1;
	while (i > 1)
		v *= i--;
	return(v);
}

main()
{
	printf("fact1(5) = %u\n", fact1(5));
	printf("fact2(5) = %u\n", fact2(5));
	printf("fact3(5) = %u\n", fact3(5));
	printf("fact4(5) = %u\n", fact4(5));
}
--------------------------------------------------------------
At the end of the journey don't except me to stay - Pink Floyd
						Obscured by Clouds

Jeff Anton	anton@postgres.berkeley.edu

paul@csnz.nz (Paul Gillingwater) (08/01/88)

In article <1670001@hpcilzb.HP.COM> tedj@hpcilzb.HP.COM (Ted Johnson) writes:
>
>Can someone please explain the difference between "reentrant" and
>"non-reentrant" code?  Thanks!

Many of us down-under probably don't bother replying to questions
because the delays means that it's already been answered, but what
the heck, here goes anyway....   ;-)

Back in 1979 when I was writing multi-tasking operating systems for
M6800's for process control applications, I found out the hard way 
the difference.  When I wrote modules that might need to be called
concurrently to respond to different sources of interrupts, I found
that if an interrupt was currently being processed (and it had to be
done real fast so no use setting a flag to process later) when another
interrupt came in, the code would  be executed again (i.e. re-entered)
before the first call could finish and clean up after itself.

The result of this was that any local variables (read absolute addresses
to RAM - this is M6800 assembler, remember!) were overwritten.  The
solution was simple - just use the X register to maintain a heap - sort
of like a stack, only starting at the bottom of RAM.  Any local variables
then became just offsets from this pointer (which WAS maintained at a
fixed place, and had to be accessed under special Test-and-set semaphore
control.  Of course any decent HLL will look after local variables in a clean
manner.

So, to summarise, re-entrant code doesn't mind being entered any number of
times (subject to stack/heap space), and won't do things that can't be
undone.  It cleans up after itself (if well written), whilst non-re-entrant
code (like the IBM ROM BIOS) doesn't and isn't.

MS-DOS - Just say Gnu!
-- 
Paul Gillingwater, Computer Sciences	Call this BBS - Magic Tower (24 hours)
paul@csnz.nz  (vuwcomp!dsiramd!csnz)	NZ +64 4 753 561 8N1 TowerNet software
P.O.Box 929, Wellington, NEW ZEALAND	V21/V23/V22/V22bis/Bell 103/Bell 212A
Vox: +64 4 846194, Fax: +64 4 843924	"All things must parse"-ancient proverb

bzs@prep.ai.mit.edu (Barry Shein) (08/06/88)

The simplest way to understand re-entrant code is to just understand
that functionally it requires that all variables accessed (plus or
minus intended global side-effects, a special case) are private to the
invocation of the subroutine, like automatic variables (a C routine
with no statics or globals is re-entrant because all automatics are
stack relative, perhaps somewhere there is an implementation which
violates that but it would be a questionable implementation.)

If you think of it in psuedo-assembler consider:

	ADD	A,B,C		# add A to B yielding C
A:	.WORD
B:	.WORD
C:	.WORD

NOT re-entrant since a subsequent entry to this code would modify
the same A,B,C locations.

	ADD	0(FP),4(FP),8(FP)

If FP is a pointer to an area provided on entry to the code and every
entry will have its own FP value (typically a stack area, but not
necessarily) then no matter how many times you are simultaneously
executing that area of code the source and destinations will be
to/from different areas of memory, voila', re-entrant.

There's nothing more to it.

The usefulness is obvious, sometimes while running a piece of code you
can get an interrupt (eg. running an output routine and a timer goes
off, say you were doing a printf() when the clock DINGed) and then,
before the interrupt is finished, it calls the same code (eg.  the
timer handler uses printf().) If printf() were re-entrant then it
would work out, returning from the interrupt later would continue with
your original printf().) Note: printf() is inherently non-re-entrant
(why? because it will always modify the stdout buffer, thus you could
get output interleaved or worse if FILE * items weren't updated
correctly when the interrupt occurred.) That example is purposely bad
to cause you to think thru the issues.

	-Barry Shein, Whereabouts Unknown