[comp.sys.atari.st] A Lesson Learned

csrobe@ICASE.EDU (Charles S. [Chip] Roberson) (03/21/89)

Greetings fellow psycho-programmers!  Well, I just learned a lesson today
and I thought I pass it on to any others who haven't learned this, yet.
It only blew a day and a half of my time.

Below is a pretty slick fprintf() statement from some of the UUPC/ST code
munged enough to simplify this example.  Looking at it, I was sure that it
should work.  I mean, why not?  It's pretty straight forward -- pass
fprintf() 3 arguments: stderr, the appropriate format string, and finally
the desired value to print.  Then once the value is passed, increment.
Right? Wrong!

--------------------------------------------------------------------------
#include <stdio.h>
#include <ctype.h>

unsigned char charlie = 'I';

void oops() {
    unsigned char *cp;

	cp = charlie;
	fprintf( stderr, isprint(*cp)? "[%c]":"[%02x]", *cp++ );
}
--------------------------------------------------------------------------

MWC passes parameters in reverse order.  That means that the "*cp++"
gets evaluated BEFORE the "isprint(*cp)..." stuff.  So, everytime
fprintf will get passed the approprate character, but isprint is going
to look at whatever is in the next data location.  [In my case that was
usually garbage in an unused portion of a character buffer!]

For those of you who need to see the nasty stuff, here is a segment of the
MWC assembly code.  It should explain everything.  Especially, note the
lines that I marked with an '*'.  [Thanks to MWC's db pgm!]

--------------------------------------------------------------------------
        movea.l -8(a6), a0	;; move *cp into a0 for passing to fprintf()
*       addq.l  $1, -8(a6)	;; increment cp as specified in fprintf arg
        clr.w   d0		;; clean out cruft in d0
        move.b  (a0), d0	;; load value destined for passing to fprintf()
        move    d0, -(a7)	;; push it on the stack
*       movea.l -8(a6), a0	;; move *cp into a0 for isprint() check
        clr.w   d0		;; clean out some more cruft
        move.b  (a0), d0	;; stash *cp in d0 for testing
        swap.w  d0		;; toss it into the upper word
        clr.w   d0		;; clean out the lower word
        swap.w  d0		;; move *cp value back into lower word
        movea.l d0, a0		;; prep for isprint()
        adda.l  $_ctype_+1, a0	;; mung it a little
        move.b  (a0), d0	;; mung it some more
        ext.w    d0		;; ok, it's munged
        andi    $171, d0	;; does it map to printable set?
        beq.s   L10006		;; nope, go print it in hex
        pea     L37		;; yes, use character format in fprintf
        bra.s   L10007		;; go finish pushing args
L10006:
        pea     L38		;; use, hexidecimal format in fprintf
L10007:
        pea     _stderr_	;; finally, pass 1st arg to fprintf
        jsr     fprintf_	;; go do it!
--------------------------------------------------------------------------

The moral of this story?  Well, as my Theory of Prog. Lang prof would
say, "That's just another insecurity of C!"; to which he would add
"Ada is a much better language."  Well, I guess I would say
"Hey! Be careful out there."

Seriously, this is one side-effect that I hadn't thought about.  How
do other ST compiler's pass their parameters?  So much for portability,
eh?  Does anybody know how the dpANS standard would affect this, if at
all?  I can't seem to remember anything in it that would.

oh well.
cheers,
-c

rob@kaa.eng.ohio-state.edu (Rob Carriere) (03/21/89)

In article <8903202254.AA00303@icase.edu> csrobe@ICASE.EDU 
(Charles S. [Chip] Roberson) writes:
[...]
>	fprintf( stderr, isprint(*cp)? "[%c]":"[%02x]", *cp++ );
[doesn't work]
>The moral of this story?  Well, as my Theory of Prog. Lang prof would
>say, "That's just another insecurity of C!"; to which he would add
>"Ada is a much better language."  Well, I guess I would say
>"Hey! Be careful out there."

He may say that, but it'd be false.  Order of parameter evaluation is
*undefined* in both C and ADA.  In other words, can do in ADA, no prob!

The *real* moral of the story is:  Thou shalst not rely on the order
in which side-effects in the parameters thou art passing to thy
function shall take place, for that order hath not been standardized. 

>Seriously, this is one side-effect that I hadn't thought about.  How
>do other ST compiler's pass their parameters?  So much for portability,
>eh?  Does anybody know how the dpANS standard would affect this, if at
>all?  I can't seem to remember anything in it that would.

No, twon't.  No problem though, just say no to side-effects in
function arguments.

SR

bam@rudedog.SGI.COM (Brian McClendon) (03/21/89)

In article <8903202254.AA00303@icase.edu>, csrobe@ICASE.EDU (Charles S. [Chip] Roberson) writes:
[stuff deleted]
> 	fprintf( stderr, isprint(*cp)? "[%c]":"[%02x]", *cp++ );
[stuff deleted]
> The moral of this story?  Well, as my Theory of Prog. Lang prof would
> say, "That's just another insecurity of C!"; to which he would add
> "Ada is a much better language."  Well, I guess I would say
> "Hey! Be careful out there."
> 
> Seriously, this is one side-effect that I hadn't thought about.  How
> do other ST compiler's pass their parameters?  So much for portability,
> eh?  Does anybody know how the dpANS standard would affect this, if at
> all?  I can't seem to remember anything in it that would.
> 

This isn't a problem with MWC.  There is no definition of what order
arguments are "executed" in the C bible (K&R) that I could find.  While
the fprintf(...) is slick code, it is also machine-dependent and VERY
un-portable (I think the Clipper (RISC) compiler also executes parameters
 backwards).

--
			- brian

   --------------------------------------------------------------------------
   Brian McClendon bam@rudedog.SGI.COM ...!uunet!sgi!rudedog!bam 415-335-1110
   --------------------------------------------------------------------------

root@yale.UUCP (Celray Stalk) (03/22/89)

In article <8903202254.AA00303@icase.edu> csrobe@ICASE.EDU (Charles S. [Chip]
Roberson) writes:
>Greetings fellow psycho-programmers!  Well, I just learned a lesson today
>and I thought I pass it on to any others who haven't learned this, yet.
>It only blew a day and a half of my time.
>
>Below is a pretty slick fprintf() statement from some of the UUPC/ST code
>munged enough to simplify this example.  Looking at it, I was sure that it
>should work.  I mean, why not?  It's pretty straight forward -- pass
>fprintf() 3 arguments: stderr, the appropriate format string, and finally
>the desired value to print.  Then once the value is passed, increment.
>Right? Wrong!
>
>--------------------------------------------------------------------------
>	fprintf( stderr, isprint(*cp)? "[%c]":"[%02x]", *cp++ );
>--------------------------------------------------------------------------
>
>The moral of this story?  Well, as my Theory of Prog. Lang prof would
>say, "That's just another insecurity of C!"; to which he would add
>"Ada is a much better language."  Well, I guess I would say
>"Hey! Be careful out there."
>
>Seriously, this is one side-effect that I hadn't thought about.  How
>do other ST compiler's pass their parameters?  So much for portability,
>eh?  Does anybody know how the dpANS standard would affect this, if at
>all?  I can't seem to remember anything in it that would.

This is a valuable lesson for people to learn: don't write code that
depends on undefined features of a language.  Order of evaluation of
operands in expressions is not defined in many languages, Ada and C
included (excecpt for logical operators and the "," operator in C).
This means that code such as the above that depends on order of
evaluation is not correct and certainly not likely to be portable.
One might want to know out of curiousity in what order other ST
comiplers pass their parameters, but you should never write code that
depends on this knowledge since it will not be correct according to
the language definition and will not be portable.

Operators to be careful of in C include ++, --, =, etc., but also
function calls.  For example, z = f(x) + y; can have unpredictable
results if f happens to change y.  Even with assignment statements,
you can't assume the right hand side will be completely evaluated
before the left, e.g.  { x=1; a[x++] = x + 3; } might cause either 4
or 5 to be stored into a[1], depending on when your compiler chooses
to evaluate the subscript.
==================================================
| Michael Fischer                                |
|    Arpanet:    <fischer-michael@cs.yale.edu>   |
|    Bitnet:     <fischer-michael@yalecs.bitnet> |
|    UUCP:       <fischer-michael@yale.UUCP>     |
==================================================

scott@applix.UUCP (Scott Evernden) (03/22/89)

In article <8903202254.AA00303@icase.edu> csrobe@ICASE.EDU (Charles S. [Chip] Roberson) writes:
>Greetings fellow psycho-programmers!  Well, I just learned a lesson today

>	fprintf( stderr, isprint(*cp)? "[%c]":"[%02x]", *cp++ );
>}
>--------------------------------------------------------------------------
>
>MWC passes parameters in reverse order.  That means that the "*cp++"
>gets evaluated BEFORE the "isprint(*cp)..." stuff. 

   Reverse compared to to what?  There is no defined "order" for arguments
   to be evaluated or pushed onto the stack (assuming you're using a stack,
   which you shouldn't assume!).

>The moral of this story?  Well, as my Theory of Prog. Lang prof would
>say, "That's just another insecurity of C!";

>... So much for portability,

   C is quite portable if you don't try to do cute things like the above
   which are defined to be non-portable.  This isn't a "gotcha", but
   something you simply need to learn.  What would you expect from, say:

	*ptr++ = ptr[5] + 2;

   Well, you'd be screwing yourself if you wrote this code, for the
   same reasons.  There are plenty of similar examples.

-scott

greg@bilbo (Greg Wageman) (03/24/89)

In article <8903202254.AA00303@icase.edu> csrobe@ICASE.EDU (Charles S. [Chip] Roberson) writes:
>Greetings fellow psycho-programmers!  Well, I just learned a lesson today
>and I thought I pass it on to any others who haven't learned this, yet.
>It only blew a day and a half of my time.
>
>	fprintf( stderr, isprint(*cp)? "[%c]":"[%02x]", *cp++ );
>
>MWC passes parameters in reverse order.  That means that the "*cp++"
>gets evaluated BEFORE the "isprint(*cp)..." stuff.  So, everytime
>fprintf will get passed the approprate character, but isprint is going
>to look at whatever is in the next data location.  [In my case that was
>usually garbage in an unused portion of a character buffer!]
>
>Seriously, this is one side-effect that I hadn't thought about.  How
>do other ST compiler's pass their parameters?  So much for portability,
>eh?  Does anybody know how the dpANS standard would affect this, if at
>all?  I can't seem to remember anything in it that would.

"The C Programming Language", Kernighan & Ritchie, Second Edition,
page 53, reproduced verbatim:

	"Similarly, the order in which function arguments are
	evaluated is not specified, so the statement

		printf("%d %d\n", ++n, power(2, n)); /* WRONG */

	can produce different results with different compilers,
	depending on whether n is incremented before power is called.
	The solution, of course, is to write

		++n;
		printf("%d %d\n", n, power(2, n));"

It's a shame you wasted so much time on a well-documented aspect of
the C language.  I guess you didn't RTFM. :-)

What gets *me* ticked off is when I *do* RTFM, and TFM is *wrong*.


Longish .signature follows.  Skip now, or don't complain!

Greg Wageman			DOMAIN: greg@sj.ate.slb.com
Schlumberger Technologies	UUCP:   ...!uunet!sjsca4!greg
1601 Technology Drive		BIX:    gwage
San Jose, CA 95110-1397		CIS:    74016,352
(408) 437-5198			GEnie:  G.WAGEMAN
------------------
Opinions expressed herein are solely the responsibility of the author.
(And the author wouldn't have it any other way.)

csrobe@ICASE.EDU (Charles S. [Chip] Roberson) (03/28/89)

In Info-Atari16 Digest   Friday, March 24, 1989   Volume 89 : Issue 108
Scott Evernden (mailrus!ulowell!m2c!applix!scott@tut.cis.ohio-state.edu)
writes:

>In article <8903202254.AA00303@icase.edu> csrobe@ICASE.EDU (Charles S. [Chip] Roberson) writes:
>>Greetings fellow psycho-programmers!  Well, I just learned a lesson today
>
>>	fprintf( stderr, isprint(*cp)? "[%c]":"[%02x]", *cp++ );
>>}
>>--------------------------------------------------------------------------
>>
>>MWC passes parameters in reverse order.  That means that the "*cp++"
>>gets evaluated BEFORE the "isprint(*cp)..." stuff. 
>
>   Reverse compared to to what?  There is no defined "order" for arguments
>   to be evaluated or pushed onto the stack (assuming you're using a stack,
>   which you shouldn't assume!).
>

Apparently, I stepped on a few people's toes with the wording I used in
my message.  First, I have to say my choice of wording above was incorrect.
The phrase "reverse order" was just a quickly (and apparently poorly)
chosen one.  Let me state, that I am well aware that order of parameter
evaluation IS undefined in C.  What I wanted to emphasize was that after
programming in C for over 5 years (with bouncing back to Pascal, Ada,
LISP, and Prolog) I still got caught by interaction.  I guess since I would
have never thought of doing that trick, I didn't see the consequences.

Second, The IMPORTANT thing to remember is, when the same variable appears
twice on the command line (or ever in a MACRO) be careful with operators such
as '--' and '++'.

Ok, here is what the MWC manual says and what I should have said:
"Programs that depend upon specific details of these calling conventions
may not be portable to other processors and other C compilers."

"In general, Mark Williams C pushes arguments from RIGHT to LEFT."

>>The moral of this story?  Well, as my Theory of Prog. Lang prof would
>>say, "That's just another insecurity of C!";
>
>>... So much for portability,
>
>   C is quite portable if you don't try to do cute things like the above
>   which are defined to be non-portable.  This isn't a "gotcha", but
>   something you simply need to learn.  What would you expect from, say:
>
>	*ptr++ = ptr[5] + 2;
>
>   Well, you'd be screwing yourself if you wrote this code, for the
>   same reasons.  There are plenty of similar examples.

Again, I had several knights rise to the defense of C, so I must assume
that my use of the word "insecurity" offended more people.  This is a
standard term used when talking about languages.  EVERY LANGUAGE HAS
INSECURITIES -- areas of vulnerability.  It does not make the language
bad or worthless -- it just means that there are ways of doing things
that produce results that aren't obvious to the programmer and are not
readily apparent from the syntax and semantics.

This is from Thomas Plum's _Notes_on_the_Draft_C_Standard_:

"The <stdarg.h> header provides a portable method for accessing variable-length
argument lists.  A function that uses this method can access its named
parameters directly, just by using their names as usual.  But it can also
access further, un-named argument values which were passed by the calling
function."

This does not avert the above insecurity, but does put in place a mechanism
for providing portable access to var-args -- which RIGHT->LEFT parameter
passing facilitates.

Anyway, it was not my intention to offend C but to point out something
that I felt could catch anyone.  [I had even made a sample three line
program to test the code fragment in isolation, and left off the '++'
because I felt it wasn't important.  A temporary lapse of reason, I
suppose.]

For anyone who cares, C is my favorite language.

>-scott

>-c
|Chip Roberson                ARPANET:  csrobe@cs.wm.edu                  |
|Dept of Comp. Sci.                     csrobe@icase.edu                  |
|College of William and Mary  BITNET:   #csrobe@wmmvs.bitnet              |
|Williamsburg, VA 23185       UUCP:     ...!uunet!pyrdc!gmu90x!wmcs!csrobe|

[	The Animal-Rights mailing list is now on the air.		]
[	Send mail to Animal-Rights-Request@cs.odu.edu to subscribe.  -c	]

andrew@uxm.sm.ucl.ac.UK (Andrew Dawson) (03/30/89)

In issue 106, Charles S. [Chip] Roberson <csrobe@icase.edu>, writes:

(extracts only quoted below)
> Below is a pretty slick fprintf() statement from some of the UUPC/ST code
> I was sure that it should work. It's pretty straight forward -- pass
> fprintf() 3 arguments: stderr, the appropriate format string, and finally
> the desired value to print.  Then once the value is passed, increment.
>	fprintf( stderr, isprint(*cp)? "[%c]":"[%02x]", *cp++ );
> MWC passes parameters in reverse order.  That means that the "*cp++"
> gets evaluated BEFORE the "isprint(*cp)..." stuff .....
> .... How do other ST compiler's pass their parameters?  So much for
> portability... Does anybody know how the dpANS standard would affect this,
> if at all?  I can't seem to remember anything in it that would.

K&R 2nd edition states:
"The order in which function arguments are evaluated is not specified,
so the statement
    printf("%d %d\n", ++n, power(2,n));    /* WRONG */
can produce different results with different compilers, depending on
whether n is incremented before power is called. The solution is to
write
    ++n;
    printf("%d %d\n", n, power(2,n));      "

Most compilers I've used on the ST seem to evaluate right to left.
Note in particular, that the system interfaces gemdos/bios/xbios
expect to receive parameters this way, so any compiler that works the
other way would have to incorporate argument reversing code for these
functions.


Andrew Dawson
School of Medicine Computer Unit
University College London