[comp.lang.c] problems/risks due to programming language, stories requ

a563@mindlink.UUCP (Dave Kirsch) (03/14/90)

> amull writes:
> 
> Msg-ID: <775@s5.Morgan.COM>
> Posted: 10 Mar 90 17:50:32 GMT
> 
> Org.  : Morgan Stanley & Co. NY, NY
> Person: Andrew P. Mullhaupt
> BTW - fall through and the 'double duty' break keyword are
> definitely examples of C flaws.  If you must, flame me, but in
> comp.lang.c, (OK?)
> 
> Later,
> Andrew Mullhaupt

C flaws?  Do this in Pascal:

switch (i) {
  case 2 :  /* Add 2 to j */
    j++;
  case 1 :  /* Add 1 to j */
    j++;
  case 0 :  /* Print j out */
    printf("%d\n", j);
    break;
  default :
    printf("Illegal value in switch.\n");
    abort();
}

In pascal you have to:

case i of
  2 : begin /* Add 2 to j */
        j := j + 2;
        writeln(j);
      end;
  1 : begin /* Add 1 to j */
        j := j + 1;
        writeln(j);
      end;
  0 : begin /* Print j out */
        writeln(j);
      end;
  else begin
         writeln('Illegal value in case.');
         Halt;
       end;
end;

If you take a look at this, you can see how C's break inside a switch is very
conveinent.  I admit, when I started learning C from when I was a Pascal
programmer I thought it was strange, too.  But I never saw it as a 'flaw'.  I
reliezed there must be a reason for it being like that, now that I'm using C, I
want it like that.  For large switch statements, you can do some really good
code reduction using that technique.  If you comment it well, it looks fine and
runs fine.

--
_____________________________________________________________________
Dave Kirsch           UUCP: {uunet,ubc-cs}!van-bc!rsoft!mindlink!a563
Voice: (604) 327-4404       a563@mindlink.UUCP
                      Vancouver, British Columbia

a563@mindlink.UUCP (Dave Kirsch) (03/15/90)

> raymond writes:
> 
> Msg-ID: <1990Mar15.205909.7882@agate.berkeley.edu>
> Posted: 15 Mar 90 20:59:09 GMT
> 
> Org.  : U.C. Berkeley
> Person: Raymond Chen
> 
> In article <1306@mindlink.UUCP> a563@mindlink.UUCP (Dave Kirsch) writes:
> >C flaws?  Do this in Pascal:
> >
> >switch (i) {
> >  case 2 :  /* Add 2 to j */
> >    j++;
> >  case 1 :  /* Add 1 to j */
> >    j++;
> >  case 0 :  /* Print j out */
> >    printf("%d\n", j);
> >    break;
> >  default :
> >    printf("Illegal value in switch.\n");
> >    abort();
> >}
> 
> Sure
> 
>         case i of
>           2 : begin j := j + 1; goto 1; end;
>           1 : begin
> 1:                  j := j + 1; goto 0; end;
>           0 : begin
> 0:                  writeln(j); end;
>           else begin writeln('Illegal value in case.'); Halt; end;
>         end;
> 
> Remember:  C's switch statement is a thinly-disguised computed goto.
>   If you're going to use goto's you may as well use them clearly
>   and explicitly.
> 
> I never use fallthrough%.  If I need fallthrough, I make the
> fallthrough EXPLICIT via goto's.  That way EVERYBODY knows what I'm
> doing:  You, me, and lint.
> 
> And of course it incurs absolutely NO performance penalty.  (The
> compiler can't cache register values across "case" labels since
> they are just labels for the computed goto.)  The only possible
> penalty is from a compiler which lacks a peephole optimizer.  (You
> just have to elide the redundant jump statement.)
> --
> % Well, rarely.  I'll use fallthrough when writing my entries into the
>   International Obfuscated C Code Contest.

Yuck!  Let's do a bit on readability, that Pascal code is gross!  Aye, it
works, but doesn't do much for readability.  I can scan through the C code in
one pass and see what it does, I have to look at the Pascal example you have
there a few times just to figure out what your doing.  Fall through DOES have
it's benefits, eg if I have a 100 line code piece in a case, but the case
before wants to do 10 lines before it, I don't want to copy the whole 100 lines
over to the other case [though I could, as most compilers (TC and MSC do) will
optimize and one compile one copy].  Jumping between cases with goto's doesn't
agree with MY programming style.  Only time I EVER use goto is when I want to
jump out of two or more inner loops, rather than using a flag variable.

I guess it's a choice of taste, although the C code is still more effecient
than the Pascal code, as the C code 'falls through', and the Pascal code
generates two uncessary jumps with the gotos.

The fall through is NOT a computed goto.  The actual code is linear.  The C
compiler I use makes a jump table for each of the cases, loads up the correct
spot to jump to, and then jumps there.  Once it's jumped to the 'case 2:' it
will execute 'j++;' then fall through to the 'case 1:'.  There is no jump
between them.  When you put a break in, it generates a jump.

--
_____________________________________________________________________
Dave Kirsch           UUCP: {uunet,ubc-cs}!van-bc!rsoft!mindlink!a563
Voice: (604) 327-4404       a563@mindlink.UUCP
                      Vancouver, British Columbia

raymond@purina.berkeley.edu (Raymond Chen) (03/16/90)

In article <1306@mindlink.UUCP> a563@mindlink.UUCP (Dave Kirsch) writes:
>C flaws?  Do this in Pascal:
>
>switch (i) {
>  case 2 :  /* Add 2 to j */
>    j++;
>  case 1 :  /* Add 1 to j */
>    j++;
>  case 0 :  /* Print j out */
>    printf("%d\n", j);
>    break;
>  default :
>    printf("Illegal value in switch.\n");
>    abort();
>}

Sure

	case i of
	  2 : begin j := j + 1; goto 1; end;
	  1 : begin 
1:	            j := j + 1; goto 0; end;
	  0 : begin
0:	            writeln(j); end;
	  else begin writeln('Illegal value in case.'); Halt; end;
	end;

[It's been some time since I last programmed in Pascal, so I may
 have misplaced a semicolon or two.]

Remember:  C's switch statement is a thinly-disguised computed goto.
  If you're going to use goto's you may as well use them clearly
  and explicitly.

I never use fallthrough%.  If I need fallthrough, I make the
fallthrough EXPLICIT via goto's.  That way EVERYBODY knows what I'm
doing:  You, me, and lint.

And of course it incurs absolutely NO performance penalty.  (The
compiler can't cache register values across "case" labels since
they are just labels for the computed goto.)  The only possible
penalty is from a compiler which lacks a peephole optimizer.  (You
just have to elide the redundant jump statement.)
--
% Well, rarely.  I'll use fallthrough when writing my entries into the
  International Obfuscated C Code Contest.

CMH117@psuvm.psu.edu (Charles Hannum) (03/16/90)

In article <1306@mindlink.UUCP>, a563@mindlink.UUCP (Dave Kirsch) says:
>
>switch (i) {
>  case 2 :  /* Add 2 to j */
>    j++;
>  case 1 :  /* Add 1 to j */
>    j++;
>  case 0 :  /* Print j out */
>    printf("%d\n", j);
>    break;
>  default :
>    printf("Illegal value in switch.\n");
>    abort();
>}

Why not this:

  if (i>2)
  {
      printf("Illegal value (dumbo!)\n");
      abort();
  }
  else printf("%d\n",j+=i);

Looks a little simpler to me ...


Virtually,
- Charles Martin Hannum II       "Klein bottle for sale ... inquire within."
    (That's Charles to you!)     "To life immortal!"
  cmh117@psuvm.{bitnet,psu.edu}  "No noozzzz izzz netzzzsnoozzzzz..."
  c9h@psuecl.{bitnet,psu.edu}    "Mem'ry, all alone in the moonlight ..."

ok@goanna.oz.au (Richard O'keefe) (03/16/90)

In article <1306@mindlink.UUCP>, a563@mindlink.UUCP (Dave Kirsch) writes:
> Do this in Pascal:
> switch (i) {
>   case 2 :  /* Add 2 to j */
>     j++;
>   case 1 :  /* Add 1 to j */
>     j++;
>   case 0 :  /* Print j out */
>     printf("%d\n", j);
>     break;
>   default :
>     printf("Illegal value in switch.\n");
>     abort();
> }

Just for grins, I'll do it in C:
	if (i < 0 || i > 2) {
	    printf("Illegal value %d in switch.\n", i);
	    abort();
	}
	j += i;
	printf("%d\n", j);

More seriously, I have often run into the "this case is just like that
only a bit different" problem several times.  Trouble is, that with more
than one minor variant, it is hard to make them all fall through.  It
can even be worth nesting your switches:
	switch (i) {
	    case 1: case 2: case 3:
		switch (i) {
		    case 1: ... ; break;
		    case 2: ... ; break;
		    case 3: ... ; break;
		}
		/* stuff common to cases 1, 2, and 3 */
		break;
	    ... and so on ...
	}
If the cost of the extra switch() is a significant part of the cost
of your program, you have an amazingly cheap program.  The reason that
I sometimes use this approach is that generally I don't *quite* want a
fall-through exactly.  Suppose I want
	case 1: i++; COMMON PART
	case 2: j++; COMMON PART
To exploit fall-through here, I would have to use
	case 1: j--; i++;
	case 2: j++; COMMON PART
which is *really* ugly and unmaintainable.

The one case where I do regularly use fall-through without any trace
of guilt is when "unwinding" side effects.  E.g.

    {
	int state = 0;

	f = fopen(...);
	if (!f) goto ERROR;
	state = 1;
	p = malloc(...);
	if (!p) goto ERROR;
	state = 2;
	...
	return OK_STATUS;
ERROR:	switch (state) {
	    case 2: free(p);
	    case 1: fclose(f);
	    case 0: break;
	}
	return ERROR_CODE;
    }

freek@fwi.uva.nl (Freek Wiedijk) (03/16/90)

In article <90074.204700CMH117@psuvm.psu.edu> CMH117@psuvm.psu.edu
(Charles Hannum) writes:
>
>      printf("Illegal value (dumbo!)\n");
                              ^^^^^

This is much too FLIPPANT!  Please, be serious (& program in Ada)!

--
Freek "the Pistol Major" Wiedijk                  Path: uunet!fwi.uva.nl!freek
#P:+/ = #+/P?*+/ = i<<*+/P?*+/ = +/i<<**P?*+/ = +/(i<<*P?)*+/ = +/+/(i<<*P?)**

amull@Morgan.COM (Andrew P. Mullhaupt) (03/17/90)

In article <1306@mindlink.UUCP>, a563@mindlink.UUCP (Dave Kirsch) writes:
> 
> C flaws?  Do this in Pascal:
> 
> switch (i) {
>   case 2 :  /* Add 2 to j */
>     j++;
>   case 1 :  /* Add 1 to j */
>     j++;
>   case 0 :  /* Print j out */
>     printf("%d\n", j);
>     break;
>   default :
>     printf("Illegal value in switch.\n");
>     abort();
> }
> 
> In pascal you have to:
   [... stuff which it turns out is needlessly complex deleted...]

My approach to your fragment is:


   if i in [0..2] then
       begin
           j := j + i; writeln(j);	(* optional semicolon religion *)
       end
   else
       writeln('Illegal value in switch.');
           (* dubious message for strict compatibility with C version *)

Seems to me you've gone a little over the top with C side effect
operators. They should get about 1/3 the use they actually do. Now
I would still prefer the C version to use 'j = j + i' as opposed to
'j += i' but a lot of C programmers who like to think about operator
precedence will prefer the latter. 

Now I think I know what you're driving at, but before getting to that,
let me point out that your code only looked right to you because you
feel comfortable with the sometimes tangled C syntax. I would guess
that your example indicates that you're too comfortable with it. This
is the kind of thing that can happen to you if you let all the people
who tell you 'after you get used to thinking in C you'll see why it's
better our way...' subvert your standards of thought. 

There is a little fragment called "Duff's device" (which can be found
in one of Bjarne Sjostroup's (spelling?) books). It is an example where
fall-through is about as elegant as fall-through gets, and it involves
writing a loop and a case statement together. It's clever, but it breaks
some C compilers. It too, is a good example of the fallacious benefit
of fall-through. It is supposed to gain some run-time advantage, but
unless your compiler is really old, this benefit is equally available
from much simpler code. Now it's a museum piece.

Disclaimer: I am not a Pascal fanatic. It has a couple rough spots 
(the nontrivial use of 'with' clauses is an accident waiting to 
happen, for one example), but I think C as practiced requires a higher
tolerance for risky code than a lot of other languages. A lot of
people who only program in C don't mind this. (It's much like the APL
community in that respect...) But guys like me who have to work in
two dialects of APL, FORTRAN, and C (I have to glue one of the APL
dialects to FORTRAN through C...) and have worked extensively in the
past in other mixtures, are not going to buy this line. It all boils
down to whether or not you believe in David Gries' principle that you
should program INTO a programming language, not IN one. 

Later,
Andrew Mullhaupt