[comp.lang.c] Oh noooooo!!

djones@megatest.UUCP (Dave Jones) (09/06/89)

Hi. It's me. I've been gone from comp.lang.c for several months now.
(I'll wait for the applause to die down. .. Now. That's better.)
Why the return? Well, as you probably know, the subject of the "goto"
statement comes up here rather often, leading to crazed shouting matches.

Oh nooooooo!!

About a year or two ago, I brazenly stated that I thought the
goto-statement was not necessarily a hanging offense. I was astounded
by the reaction. People posted messages stating flat out that I was
an incompetent programmer, and that software from any company unfortunate
enough ever to have had me on the payroll was suspect at best. They were
adamant; rude, I thought. I pouted.

At the time, I wanted to post an example benign goto, but grepping through the
hundred thousand or so lines of my code which I had on line yielded not one
goto of any kind. Nope, not a goto in the lot. Oh well.

But be of good cheer! I just now produced one, which I will be happy to
share with you.

The following is from a yacc-lookalike program, which I am tentatively
calling "Molly McYacc". It's no deal, Home. I started it three working
days ago, and expect to be finished sometime tonight.  And the goto stays,
thank-you-very-much.



#include "lr1.h"

/**********************************************************************
 * Verify that all nonterminal symbols can derive some string of
 * terminal symbols. If a rule derives the empty string, make particular
 * note of that. We'll use that info when generating follow-sets.
 *
 * All nonterminal symbols initially have their "derives" variable
 * set to "dont_know". That will be changed to one of these, as
 * defined in dict.h:
 *
 *    derives_nothing     This indicates an error in the grammar.
 *    derives_something   Derives some string, but not the empty string.
 *    derives_empty       Derives the empty string.
 */

void
check_derivations()
{
  Hash_iter next;
  Symbol* sym;

  /* Iterate through all symbols. */
  Hash_iter_init(&next, &symbols.table);

  while(sym = (Symbol*)Hash_iter_next(&next))
    if(sym->sym_class == nonterminal) {

      if((sym->is_a.nonterminal.derives = derives(sym)) == derives_nothing)
	error_msg("Symbol %s derives nothing.\n", sym_name(sym->sym_num));

    }
}


static
derives(lsym)
  Symbol* lsym;
{
  
  Queue_iter rule_iter;
  Rule* rule;
  int we_find_it_derives;
  
  if( lsym->is_a.nonterminal.derives != dont_know)  {
    return lsym->is_a.nonterminal.derives;
  }
  
  /* While this invocation of derives() is active, 
   * we will assert that the symbol does not derive anything.
   * Thus we avoid recursive derivations, which need not be
   * considered, and would lead us into unterminated recursion.
   * Non-recursive derivations are unaffected.
   */
  
  lsym->is_a.nonterminal.derives = derives_nothing;
  we_find_it_derives = derives_nothing;
  
  /* Iterate through all the rules that derive this
   * symbol, looking for one which derives something. [sic.]
   * If one derives the empty string, take note.
   */
  Queue_iter_init(&rule_iter, &lsym->is_a.nonterminal.rules);      
  
  while(rule = (Rule*)Queue_iter_next(&rule_iter)) {
    
    /* Iterate through the right-hand-side to see if all symbols
     * there derive something. Notice if they all derive the empty
     * string or not.
     */
    
    Queue_iter rsym_iter;
    Symbol* rsym;
    int all_derive_empty = 1;
    
    Queue_iter_init(&rsym_iter, &rule->rhs);
    
    while(rsym = (Symbol*)Queue_iter_next(&rsym_iter)) {
      if(rsym->sym_class == terminal)
	all_derive_empty = 0;
      else
	switch (derives(rsym)) {
	case derives_nothing:
	  goto next_rule;
	case derives_something:
	  all_derive_empty = 0;
	  break;
	case derives_empty:
	  break;
	}
    }
    
    /* If we get here, all symbols on the rhs are proven
     * to derive something.
     */
    
    if(all_derive_empty) {
      return lsym->is_a.nonterminal.derives = derives_empty;
    }
    else {
      lsym->is_a.nonterminal.derives =
	we_find_it_derives = derives_something;
    }
    
    
  next_rule: continue;
    
  }
  
  
  if( we_find_it_derives == derives_nothing )
    lsym->is_a.nonterminal.derives = dont_know;
  
  return we_find_it_derives;
 
}

ftanaka@Apple.COM (Forrest Tanaka) (09/07/89)

In article <7598@goofy.megatest.UUCP> djones@megatest.UUCP (Dave Jones) writes:
>About a year or two ago, I brazenly stated that I thought the
>goto-statement was not necessarily a hanging offense. I was astounded
>by the reaction. People posted messages stating flat out that I was
>an incompetent programmer, and that software from any company unfortunate
>enough ever to have had me on the payroll was suspect at best. They were
>adamant; rude, I thought. I pouted.

I've been using gotos regularly in my C code for quite a few months--in one
specific situation.  That situation is for error handling.  The usual sequence
is something like this (AllocateMemory just allocates some memory and returns
a pointer to it, or 0 if there isn't enough memory.  DeallocateMemory
deallocates that memory):

#define null 0

char *
SomeFunction ()
    {
    char *Block0;
    char *Block1;
    char *Block2;

    Block0 = (char *) null;
    Block1 = (char *) null;
    Block2 = (char *) null;
    if ((Block0 = AllocateMemory ()) == (char *) null)
        goto Abort;
    if ((Block1 = AllocateMemory ()) == (char *) null)
        goto Abort;
    if ((Block2 = AllocateMemory ()) == (char *) null)
        goto Abort;
    return Block0;

Abort:
    if (Block0 != (char *) null)
        DeallocateMemory (Block0);
    if (Block1 != (char *) null)
        DeallocateMemory (Block1);
    if (block2 != (char *) null)
        DeallocateMemory (Block2);
    return (char *) null;
    }

Now, never mind that Block1 and Block2 get lost when SomeFunction returns.
This is just an example of some calls that could be made.  Also, the last if
statement in the Abort block can never be true.  I'm just trying to be
consistent here.

When I was trying to come up with a consistent and convenient way to handle
error conditions within functions, I examined what I would do if I was writing
the function in assembly language.  I would probably check for an error when
a function like AllocateMemory returns, and if there was an error, branch to
some error handling code.  Ah hah! thought I.  This is is possible in C with
the goto statement.  Now I never have to have a bunch of nested ifs and I can
deal with undoing what I had done in that function before the error occured.

Or, how 'bout this justification: When something exceptional (like an error)
occurs, something exceptional (like a goto) should happen.
*******************************************************************************
Forrest Tanaka                            AppleLink: TANAKA
Macintosh Developer Technical Support     UseNet:    ftanaka@Apple.COM
Apple Computer, Inc.

gwyn@smoke.BRL.MIL (Doug Gwyn) (09/08/89)

In article <34566@apple.Apple.COM> ftanaka@Apple.COM (Forrest Tanaka) writes:
>I've been using gotos regularly in my C code for quite a few months--in one
>specific situation.  That situation is for error handling.

Your example could be generalized as follows:

char *
SomeFunction ()
    {
    char *Block0;
    char *Block1;
    char *Block2;

    if ((Block0 = AllocateMemory ()) == NULL)
        goto Abort0;
    if ((Block1 = AllocateMemory ()) == NULL)
        goto Abort1;
    if ((Block2 = AllocateMemory ()) == NULL)
        goto Abort2;
    return Block0;

Abort2:
    DeallocateMemory (Block1);
Abort1:
    DeallocateMemory (Block0);
Abort0:
    return (char *) null;
    }

which we ended up adopting extensively in the huge programming project
I occasionally mention.  It brings a high degree of order to the error
handling process, significantly improving the chances that all actions
get unwound properly on an abort.

john@chinet.chi.il.us (John Mundt) (09/08/89)

In article <34566@apple.Apple.COM> ftanaka@Apple.COM (Forrest Tanaka) writes:
>I've been using gotos regularly in my C code for quite a few months--in one
>specific situation.

Pathetic justifications deleted and sample of code follows.

>#define null 0
>char *
>SomeFunction ()
>    {
>    char *Block0;
>    char *Block1;
>    char *Block2;
>
>    Block0 = (char *) null;
>    Block1 = (char *) null;
>    Block2 = (char *) null;
>    if ((Block0 = AllocateMemory ()) == (char *) null)
>        goto Abort;
>    if ((Block1 = AllocateMemory ()) == (char *) null)
>        goto Abort;
>    if ((Block2 = AllocateMemory ()) == (char *) null)
>        goto Abort;
>    return Block0;
>
>Abort:
>    if (Block0 != (char *) null)
>        DeallocateMemory (Block0);
>    if (Block1 != (char *) null)
>        DeallocateMemory (Block1);
>    if (block2 != (char *) null)
>        DeallocateMemory (Block2);
>    return (char *) null;
>    }
>
>When I was trying to come up with a consistent and convenient way to handle
>error conditions within functions, I examined what I would do if I was writing
>the function in assembly language.  I would probably check for an error when
>a function like AllocateMemory returns, and if there was an error, branch to
>some error handling code.  Ah hah! thought I.  This is is possible in C with
>the goto statement.  Now I never have to have a bunch of nested ifs and I can
>deal with undoing what I had done in that function before the error occured.

But you don't have to use gotos at all in that situation.  You could write
the thing this way, and save the heretical goto:


#define null (char *) 0

char * SomeFunction () {
    char *Block0, *Block1, *Block2;

    Block0 = Block1 = Block2 = null;

    if ((Block0 = AllocateMemory ()) != null
    	&& (Block1 = AllocateMemory ()) !=  null
	&& (Block2 = AllocateMemory ()) !=  null)
    return Block0;

    if (Block0 != null)
        DeallocateMemory (Block0);
    if (Block1 != null)
        DeallocateMemory (Block1);
    if (block2 != null)
        DeallocateMemory (Block2);
    return null;
}


The above code does exactly the same thing, and avoids the dreaded
goto!  I think it is just as clear as the goto coding.  

However, my hands are not clean, and there are a few gotos in my
code when I am deep into some nested somthing, as in

	while (something) {
		...
		do {
			something_grandiose()
			for ( ; this < that; ) {
				...
				while (something_else) {
					...
					if (error)
						goto sloth;
				}
			}
		} while marvelous_things();
	}
	return;

	sloth:  /* Sorry! */
		bail_out();

Granted, no one ought to nest like that, but when you do :-), the
goto is a quick way to get out without a ton of flag checking.  But
I always label the goto "sloth" so that everyone knows what kind of
person I am to put goto's  into my program in the first place.  
-- 
---------------------
John Mundt   Teachers' Aide, Inc.  P.O. Box 1666  Highland Park, IL
john@chinet.chi.il.us
(312) 998-5007 (Day voice) || -432-8860 (Answer Mach) && -432-5386 Modem  

hascall@atanasoff.cs.iastate.edu (John Hascall) (09/08/89)

In article <9511@chinet.chi.il.us> john@chinet.chi.il.us (John Mundt) writes:
}In article <34566@apple.Apple.COM> ftanaka@Apple.COM (Forrest Tanaka) writes:
}>I've been using gotos regularly in my C code for quite a few months--in one
 
}Pathetic justifications deleted and sample of code follows.
 
 
}char * SomeFunction () {
}    char *Block0, *Block1, *Block2;
 
}    Block0 = Block1 = Block2 = null;
 
}    if ((Block0 = AllocateMemory ()) != null
}    	&& (Block1 = AllocateMemory ()) !=  null
}	&& (Block2 = AllocateMemory ()) !=  null)
}    return Block0;
 
}    if (Block0 != null)
}        DeallocateMemory (Block0);
}    if (Block1 != null)
}        DeallocateMemory (Block1);
}    if (block2 != null)
}        DeallocateMemory (Block2);
}    return null;
}    }

   The code:

       if (Block2 != null)
	   DeallocateMemory (Block2);

   is useless, if Block2 != null you have already returned above!
   How about:

      #define ALLOC(ptr, type) ((ptr = (type*)malloc(sizeof(type))) != NULL)

	 ...
      
      if (ALLOC(B0, FOO)) {
              if (ALLOC(B1, BAR)) {
                      if (ALLOC(B2, BAZ)) {
                              /* all alloced ok, do something with it */
                              return (B0);
                      }
                      FREE(B1);
              }
              FREE(B0);
      }
      return (NULL);      /* couldn't alloc something */
  
  John Hascall

gwyn@smoke.BRL.MIL (Doug Gwyn) (09/09/89)

In article <9511@chinet.chi.il.us> john@chinet.chi.il.us (John Mundt) writes:
-    if ((Block0 = AllocateMemory ()) != null
-    	&& (Block1 = AllocateMemory ()) !=  null
-	&& (Block2 = AllocateMemory ()) !=  null)
-The above code does exactly the same thing, and avoids the dreaded
-goto!  I think it is just as clear as the goto coding.  

But it was just an example.  Often in practice there are non-trivial
operations between the several points of possible failure.