[comp.lang.c] Another way

rbutterworth@watmath.waterloo.edu (Ray Butterworth) (02/15/88)

I just got hit by yet another way not to write a loop:

    auto size_t index;

    index = sizeof(array) / sizeof(array[0]);

    while (--index >= 0)
        blah blah blah

This looks wonderfully portable and works fine on the BSD 4.3 compiler
(and probably most others).

But on an ANSI compiler, (size_t) will be an unsigned integer
and the loop will go forever.

Lint will notice this problem, but only with the ANSI compiler
when it is probably too late.

crowl@cs.rochester.edu (Lawrence Crowl) (02/17/88)

In article <16941@watmath.waterloo.edu>
rbutterworth@watmath.waterloo.edu (Ray Butterworth) writes:
]I just got hit by yet another way not to write a loop:
]
]    auto size_t index;
]
]    index = sizeof(array) / sizeof(array[0]);
]
]    while (--index >= 0)
]        blah blah blah
]
]This looks wonderfully portable and works fine on the BSD 4.3 compiler
](and probably most others).
]
]But on an ANSI compiler, (size_t) will be an unsigned integer
]and the loop will go forever.
]
]Lint will notice this problem, but only with the ANSI compiler
]when it is probably too late.

Fortunately, there is an easy fix.

    while ( index-- > 0 )

-- 
  Lawrence Crowl		716-275-9499	University of Rochester
		      crowl@cs.rochester.edu	Computer Science Department
...!{allegra,decvax,rutgers}!rochester!crowl	Rochester, New York,  14627

decot@hpisod2.HP.COM (Dave Decot) (02/17/88)

> In article <1988Feb11.200149.25172@sq.uucp> msb@sq.UUCP (Mark Brader) writes:
> >	Floating point numbers should never be used for counting.
> 
> Never say never.  (Well, hardly ever say never.)  At least one
> widely-used implementation of Snobol4 successfully uses a floating
> point register to count the number of statements executed, relying on
> floating point overflow being detected by hardware so that the program
> may be aborted when the user-specified statement count limit is
> reached.

We have a misunderstanding of the word "should", apparently.

Dave Decot
hpda!decot

rbutterworth@watmath.waterloo.edu (Ray Butterworth) (02/18/88)

In article <6848@sol.ARPA>, crowl@cs.rochester.edu (Lawrence Crowl) writes:
> In article <16941@watmath.waterloo.edu>
> rbutterworth@watmath.waterloo.edu (Ray Butterworth) writes:
> > I just got hit by yet another way not to write a loop:
> >     auto size_t index;
> >     index = sizeof(array) / sizeof(array[0]);
> >     while (--index >= 0)
> > This looks wonderfully portable and works fine on the BSD 4.3 compiler
> > (and probably most others).
> > But on an ANSI compiler, (size_t) will be an unsigned integer
> > and the loop will go forever.

> Fortunately, there is an easy fix.
>     while ( index-- > 0 )

Thanks (sincere, not sarcastic) to those that have sent me this
suggestion.

I was aware that this is a much better way of doing it.

The problem I was trying to point out is that much existing
code wasn't written with perfect style, and this is yet another
way in which otherwise correct code will break when it is ported
to an ANSI compiler (as I found out the hard way).

dhesi@bsu-cs.UUCP (Rahul Dhesi) (02/19/88)

>> >	Floating point numbers should never be used for counting.

>> Never say never....At least one
>> widely-used implementation of Snobol4 successfully uses a floating
>> point register to count the number of statements executed...

>We have a misunderstanding of the word "should", apparently.

There might be confusion between "should not" and "should never".
Floating point numbers should not be used for counting except when such
use gains us a remarkable degree of badly-needed efficiency.  In the
Snobol (actually SPITBOL) example, the choice was between the
equivalents of the following:

     count -=1;		/* integer arithmetic with software test */
     if (count == 0)
	abort_program;

and

     count +=x;	/* fast hardware floating add */
     /* actually   AUR   SCNT,SINC  on IBM 360 */

Note that the language required one or the other of the above to
be done before every statement executed.
-- 
Rahul Dhesi         UUCP:  <backbones>!{iuvax,pur-ee,uunet}!bsu-cs!dhesi

gwyn@brl-smoke.ARPA (Doug Gwyn ) (02/19/88)

In article <16982@watmath.waterloo.edu> rbutterworth@watmath.waterloo.edu (Ray Butterworth) writes:
-> >     auto size_t index;
-> >     index = sizeof(array) / sizeof(array[0]);
-> >     while (--index >= 0)
-The problem I was trying to point out is that much existing
-code wasn't written with perfect style, and this is yet another
-way in which otherwise correct code will break when it is ported
-to an ANSI compiler (as I found out the hard way).

The fact that the result of sizeof has an unsigned integral type is
not new with ANSI C.  UNIX C has been this way since around 1980 at
least.  You're getting confused since Berkeley didn't pick up this
change when they stole the pre-USG 3.0 C compiler (circa 1979) that
is included in their distribution.

gwyn@brl-smoke.ARPA (Doug Gwyn ) (02/19/88)

In article <2150@bsu-cs.UUCP> dhesi@bsu-cs.UUCP (Rahul Dhesi) writes:
>Floating point numbers should not be used for counting except when such
>use gains us a remarkable degree of badly-needed efficiency.  In the
>Snobol (actually SPITBOL) example, the choice was between the
>equivalents of the following:
>     count -=1;		/* integer arithmetic with software test */
>     if (count == 0)
>	abort_program;
>and
>     count +=x;	/* fast hardware floating add */
>     /* actually   AUR   SCNT,SINC  on IBM 360 */
>Note that the language required one or the other of the above to
>be done before every statement executed.

So, the Snobol implementor made a stupid decision, then.

I don't have an IBM 360 at hand, but I do have a Gould PowerNode 9080,
which has a remarkably similar architecture (including floating-point
format) to the IBM 360's.  I jury-rigged a crt0.o to enable floating-
point exception trapping (normally it has to be disabled, to avoid
trapping on integer overflow), and ran a large number of iterations
(that did nothing but iterate) using floating-point trapping to
terminate the loop, then an identical number of iterations testing an
integer counter.  The floating-point version burned 4.3 seconds of CPU
time while the integer counter version's overhead was only 0.5 seconds.

Note that the time required to perform the integer counter test against
zero is utterly insignificant by comparison with whatever work the
"statement execution" would involve, and it is portable besides.
This looks like another instance of "misplaced efficiency concerns".

cdl@mplvax.nosc.MIL (Carl Lowenstein) (02/19/88)

In article <2150@bsu-cs.UUCP> dhesi@bsu-cs.UUCP (Rahul Dhesi) writes:
>
>Floating point numbers should not be used for counting except when such
>use gains us a remarkable degree of badly-needed efficiency.  
>
>     count -=1;		/* integer arithmetic with software test */
>     if (count == 0)
>	abort_program;

This is inefficient?  I would hope that any decent compiler could turn
these lines into the something like (usual case one instruction):

			/ integer arithmetic with hardware test
	ISZ	COUNT	/ PDP-8 instruction, but even CISC's like
	JMP	ABORT	/  IBM 7090's and later have an equivalent

-- 
	carl lowenstein		marine physical lab	u.c. san diego
	{ihnp4|decvax|ucbvax}	!sdcsvax!mplvax!cdl

leichter@yale.UUCP (Jerry Leichter) (02/20/88)

Commenting about the inplementation of SPITBOL/360, which used a floating
point register to keep track of the number of statements executed, Doug
Gwyn writes:

>
>So, the Snobol implementor made a stupid decision, then.
>

He the proceeds to explain that the decision was stupid (a) because when he
compared two ways of doing this on a Gould Powernode, doing it with integers
was faster; (b) the way SPITBOL did it was "non-portable".

It's generally a good idea to understand the issues before criticizing a
design decision.  Neither Doug's comments, nor any of the others on this
topic, are based on any such understanding.  They appear to be made by people
who don't understand the language, the implementation environment, the goals
of the implementation --- the probably don't even know to within 5 years
when the implementation they are criticising was done.  How anyone can have
the chutzpa to imagine that they have ANYTHING worthwhile to say, lacking all
that basic information, would be beyond me if I hadn't seen so much flammage
on this net already.

To add a little reality:  SPITBOL/360 was done somewhere around 1971.  It was
an attempt to produce a complete implementation of all of SNOBOL4 that would
run as fast as possible.  There was already a very portable --- certainly by
the standards of the late 1960's, probably even by today's standards --- im-
plementation of SNOBOL in existence.  It took, by the standards of the day,
a huge amount of memory, and ran slowly.  SPITBOL attempted to address both
constraints.  It was willing to forgo portability, and was designed to run
on IBM/360's --- yes, 360's, the 370 wasn't to exist for several more years ---
under OS/360.

Now, SNOBOL4 is not an easy language to execute efficiently.  It allows for
things like the creation of new functions, data types, and even variables at
run time --- and these abilities are heavily used.  It allows new code to be
compiled at run time, sometimes replacing existing code.  Assignments to, or
even attempts to read from, variables can trigger I/O, or (via tracing) the
execution of arbitrary user code.  The fact that a particular access to a
particular variable may have such affects can only be determined at run time.

Enough generalities.  The particulars of the use of a floating point statement
counter are as follows:  Floating point arithmetic is quite rare in SNOBOL
programs, so there is little to be gained by keeping FP registers --- and
remember, on a 360 FP registers are completely distinct from the general
registers --- free.  On the other hand, the nature of SNOBOL, and the way
addressing works on a 360, places a premium on general registers.  Also, in
the early '70's, core was CORE --- registers were MUCH faster than main
memory.  Even FP registers and FP operations were quite fast compared to
main memory accesses.

Now, SNOBOL numbers statements, and it counts statements executed.  The
statement count must be checked at the beginning of every statement executed
against a (user-changable, at run time) statement limit.  The statement number,
on the other hand, is only used in rare circumstances --- in generating errors,
or in the (in practice) unlikely event that the user asks for it.  So the
SPITBOL implementors did something clever:  They used an otherwise-unneeded
resource (an FP register) in a clever way to solve several problems.  The
counter was kept in the FP register, and the hardware effectively did the
statement limit check.  Meanwhile, the actual instruction used --- an AUR,
as Rahul Dhesi noted, Add Unnormalized Register --- was "unusual"; in fact,
it was never generated by the compiler anywhere but at the head of a statement.
Thus, it acted as a "statement begin" marker.  No record was kept of the
current statement number --- doing that would have required tying up another
register, or an extra memory access, at each statement.  Instead, on the rare
occassions that the current statement number was needed, the run-time code
would walk through all the compiled code --- it was all linked together ---
and count AUR's until it got to the currently-executing statement.

I believe there may have been other instances in which it was necessary to
find the beginning of the current, or the next, statement; searching for an
AUR could be used for that, too.

All told, an elegant, effective, and fast solution, given the realities of
the day.

Some of the same people who design SPITBOL/360 later went on to develop
MACRO/SPITBOL, a rather interesting implementation that was designed to be
(a) portable; (b) efficient on any particular hardware.  It succeeded;
MACRO/SPITBOL on a PDP-10 was smaller, and usually faster, than SITBOL, an
implementation that had been hand-tuned for the -10.  MACRO/SPITBOL, now at
least 10 years old, continues to be available; it runs on such machines as
VAXes and 8086's, both designed well after the implementation.

							-- Jerry

dhesi@bsu-cs.UUCP (Rahul Dhesi) (02/20/88)

In article <7285@brl-smoke.ARPA> gwyn@brl.arpa (Doug Gwyn (VLD/VMB) <gwyn>) 
writes:
[re floating add versus integer decrement & test]
>So, the Snobol implementor made a stupid decision, then.
>
>[On the] Gould PowerNode 9080,
>which has a remarkably similar architecture...to the IBM 360's...
>[a test showed that the] floating-point version burned 4.3 seconds of CPU
>time while the integer counter version's overhead was only 0.5 seconds.

I don't know what Dewar and Belcher, the authors of SPITBOL, would say to
this.  Here's what Griswold says in his book "The Macro Implementation
of Snobol4" (Freeman & Co. 1972).

"An amusing example of the difference between SNOBOL4 and SPITBOL
is provided by the way &STLIMIT is implemented and statement
numbers located....[In SNOBOL4] &STLIMIT (at a known location in UKYPBL)
is checked by init1.  If the statement limit has been exceeded, control
is transferred to an error routine....
   "SPITBOL, in its quest for efficiency, approaches these matters
quite differently.  At the beginning of each statement, the following
instruction occurs.

     AUR      SCNT,SINC

This instruction increments a floating-point register reserved for
statement counting.  SCNT is set up so that when &STLIMIT is exceeded,
a program interrupt occurs because of floating-point overflow.
An interrupt handler detects this interrupt as excessive statement
execution....

[some paragraphs later]

   "...In any event, SPITBOL is someone else's story to tell.  It is
a fascinating, well-designed, and cleverly implemented system which the
serious student of software is well advised to study....SPITBOL, of
course, is hardly machine-independent or portable.  Heavy use is made
of idiosyncracies of the 360 instruction set, and the coding is very
tight."
-- 
Rahul Dhesi         UUCP:  <backbones>!{iuvax,pur-ee,uunet}!bsu-cs!dhesi

gwyn@brl-smoke.ARPA (Doug Gwyn ) (02/20/88)

In article <23450@yale-celray.yale.UUCP>, leichter@yale.UUCP (Jerry Leichter) writes:
> To add a little reality:  SPITBOL/360 was done somewhere around 1971.

Yes, and this discussion about how to iterate via floating-point exception
was being conducted in 1988.  My concern was that people not use a "trick"
unnecessarily, when the trick might not work and when the alternative is
better anyway, in the C language, which is what this newsgroup is about.

I mean, I've relied on the CDC 1700's ability to automatically indirect
arbitrarily many times, and on -0 as a special flag, and other tricks,
when programming specifically for that system (and when portable system
programming was practically impossible anyway).  But the vast majority
of programmers are not in such circumstances today.  There has been
considerable progress in understanding the real economics of software
since the 1960s and early 1970s; we have learned what is wrong with
cute system-dependent "tricks" and assembly-language programming for
general applications.

ok@quintus.UUCP (Richard A. O'Keefe) (02/20/88)

In article <722@mplvax.nosc.MIL>, cdl@mplvax.nosc.MIL (Carl Lowenstein) writes:
> In article <2150@bsu-cs.UUCP> dhesi@bsu-cs.UUCP (Rahul Dhesi) writes:
> >Floating point numbers should not be used for counting except when such
> >use gains us a remarkable degree of badly-needed efficiency.  
> >     count -=1;		/* integer arithmetic with software test */
> >     if (count == 0) abort_program;
> This is inefficient?  I would hope that any decent compiler could turn
> these lines into the something like (usual case one instruction):
> 			/ integer arithmetic with hardware test
> 	ISZ	COUNT	/ PDP-8 instruction, but even CISC's like
> 	JMP	ABORT	/  IBM 7090's and later have an equivalent

The /360 can do this with
COUNT	EQU	{some register number}
	S	COUNT,=1
	BZ	ABORT
This does, however, involve a memory reference.  If you don't want to
do a memory reference, you can dedicate a register to holding 1, or
you can keep the counter *negative* (no negative offsets!) and do
	LA	COUNT,1(COUNT)	* doesn't set the condition codes
	LTR	COUNT,COUNT	* so they must be set explicitly
	BZ	ABORT
Bearing in mind that the /360 places heavy demands on its general
registers (immediate operands are scarce, there are no absolute
address, address offsets are 0..4095, &c), maintaining the counter
and its increment in floating-point registers could have contributed
greatly to efficiency, not in this particular statement, but by making
more general registers available elsewhere.

The real point is that this was NOT a genuine instance of counting with
floating-point values.  What they *really* wanted was an integer count,
using floating-point instructions was a hack which required exceptionally
careful coding.  The advice "don't write loops like
	for (x = 23.1; x <= 23.7; x += 0.1) ....
" remains valid.

trt@rti.UUCP (Thomas Truscott) (02/20/88)

> In article <16982@watmath.waterloo.edu> rbutterworth@watmath.waterloo.edu (Ray Butterworth) writes:
> -> >     auto size_t index;
> -> >     index = sizeof(array) / sizeof(array[0]);
> -> >     while (--index >= 0)
> -... this is yet another
> -way in which otherwise correct code will break when it is ported
> -to an ANSI compiler (as I found out the hard way).

The Gould compiler says "warning: unsigned >= always true".

In article <7278@brl-smoke.ARPA>, gwyn@brl-smoke.ARPA (Doug Gwyn ) writes:
> The fact that the result of sizeof has an unsigned integral type is
> not new with ANSI C.

Yes, nor is it the cause of the infinite loop.

By the way, the suggested fix:
	while (index-- > 0)
causes the Gould compiler to say:
	"warning: degenerate unsigned comparison"
which is reasonable since the fix is misleading.  Better would be:
	while (index-- != 0)

If your compiler does not do these things (and much much more!)
complain to the vendor.  Start writing SPRs!!
You can spend the time now, or later ("the hard way").
	Tom Truscott

jbn@glacier.STANFORD.EDU (John B. Nagle) (02/21/88)

     Another classic problem is the fact that the difference between two
unsigned values is itself unsigned.  Thus,

     unsigned short a,b; 

     a = 1;
     b = 2;
     if (a - b < 0) printf("B smaller than A\n"); 

is invalid.  This particular strangeness caused an obscure bug in 4.3BSD
networking which prevented interoperation of 4.3BSD's TCP with some
other TCP implementations, including TOPS-20.