Error Handling

Vasile R. Montan vrm at blackwater.cerc.wvu.wvnet.edu
Tue Oct 2 07:17:37 AEST 1990



   Here is a summary of the responses that I recieved on my question
on the "best" way to handle errors in C.  I have reproduced my
original article in its entirety and have cut out all references to it
except Method numbers from the responses.  I appologize for repetition
of some replies, but I would like to recognize all authors who
replied.  There were also some good responses posted to comp.lang.c in
case you missed them.

   The majority seem to like Method 3 which uses a goto to impliment
an exception handler.  However, the people who are opposed to it are
often very strongly opposed to it.  Method 1 with an immediate return
when an error is encountered is second, especially if there are only a
few errors.

   Thanks again to everyone who responded.

-- Vasile

My orginal article:
>   Here is a general question to all of the expert C programmers, or
>even novice C programmers, who have a strong opinion on C style.  What
>is the best way for a function to check for errors and return an error
>code?  This sounds like a simple problem, but I don't know which way
>to go.  I would just like to follow a style which is easy to maintain.
>Below are the methods which I have considered so far.  You may add new
>ones if you like.  I apologize for any other style problems in my
>example code.  I was trying to make the examples small.
>
>   I will post a summary of all replies that I get through email.
>
>Method 1: Return Error Code When Error Is Encountered
>          The problems I have with this are first it goes against
>          software engineering principles which say that all functions
>          should have one entry point and one exit point.  Secondly
>          some functions do have some standard clean up to do.
>
>	  int function()
>	  {
>	     int error;
>             if (error = check1()) {cleanup(); return error;}
>             if (error = check2()) {cleanup(); return error;}
>             if (error = check3()) {cleanup(); return error;}
>	     cleanup();
>	     return NOERROR;
>          }
>
>Method 2: Set Error Code and Return Only at End
>	  The problem I have with this is that I don't want to do
>          later work if a previous error occured.  I just want to
>          clean up and return.  This forces me to continuously check
>          if a previous error occured.
>
>	  int function()
>          {
>	     int error;
>	     error = check1();
>	     if (!error) error = check2();
>	     if (!error) error = check3();
>             cleanup();
>             return error;
>          }
>
>Method 3: Use GOTO to Create Exception Handler
>          Of course this breaks the golden rule of software
>          engineering of "absolutely positively no GOTO's in your
>          program."
>
>	  int function()
>          {
>	     int error;
>	     if (error = check1()) goto exception;
>	     if (error = check2()) goto exception;
>	     if (error = check3()) goto exception;
>             cleanup();
>             return NOERROR;
>	  exception:
>	     cleanup();
>             return error;
>          }
>

============================================================================
>From raymond at math.berkeley.edu Thu Sep 27 13:56:28 1990
>Method 3: Use GOTO to Create Exception Handler

I've never seen that particular rule.  GOTOs are bad when they are abused.
When used properly, GOTOs can actually make code easier to read.
Error handling is, I think, one of those instances.

============================================================================
>From kanamori at Neon.Stanford.EDU Thu Sep 27 13:57:51 1990
Tell Wirth to GOTO..., no just kidding. Seriously, I would go with 
Method 1 (i.e. bail out as soon as you detect an error.) If cleanup
is required, use method 3 (goto exception handler). The tricky part here
is that depending on how far the computation proceeded before the
error manifested itself, different amounts of cleanup may be required.

"Absolutely positively no GOTO's in your program" is a bad rule to
follow. A better rule is "Make the structure of your code match the
structure of your problem." Simply put, error handling is inherently
unstructured and is best handled by an unstructured approach. By investing 
in a certain number of messy, unsightly "if...return" statements at the start 
of the function, you greatly increase the readability of the rest of the 
function because it can proceed on the assumption that all is well.
Now, the code reads straightforwardly - telling the reader what it does
in the normal case, and segregating the exceptional cases off in a corner.
It simply isn't natural for a program to say

  "Do step 1"
  "Do step 2 unless error occurred in step 1"
  "Do step 3 unless error occurred in steps 1 or 2"
  "Do step 4 unless error occurred in steps 1, 2 or 3..."


By the way, there are alternatives to having functions that return
either a result or an "error code". The problem with that approach is
that it cruds up the caller's code because it has to check every return
value - and, it's hard to design a consistent error-coding scheme that works
for all result types.

A better way is to design the function so that it either returns a valid
result or doesn't return at all. For maximum flexibility, the caller
should be able to specify how it wants the callee to handle errors: does it 
panic and core-dump, or does it escape to some global handler, or... The
most general mechanism would be for the caller to pass in an escape
procedure (say created by setjmp) but this is pretty clumsy to do in C.
You might, instead, try to list the different error handling strategies
required and assign an integer code for each one that the caller can pass
in.

Again, the advantage is that the caller can assume that the return value
is always valid. Drastic reduction in "if" statements. If a caller doesn't
want to handle an error, it can pass the buck to its own caller by passing
in the same error handler that IT was handed. Reading the code becomes
easy for the easy, normal cases, and becomes hard only for the exceptional
cases.

============================================================================
>From brnstnd at KRAMDEN.ACF.NYU.EDU Thu Sep 27 14:36:15 1990
I think you would program a lot more effectively if you unlearned all of
that ``software engineering.''

---Dan

============================================================================
>From sga at redwood.cray.com Thu Sep 27 14:54:42 1990
> Method 1: Return Error Code When Error Is Encountered
This is definately my preferred method.  There are times when I firmly
believe that the software engineering proponents never coded a real application
in their life, and this is one of those cases.  I have no problem with one
NORMAL return in a function, but errors are exceptions and should be treated
differently.

> Method 2: Set Error Code and Return Only at End
Messy and very inefficient.

> Method 3: Use GOTO to Create Exception Handler
Your last sentence says it all.

Dr. Steven Anderson
Cray Research, Inc
sga at cray.com

============================================================================
>From steve at unidata.ucar.edu Thu Sep 27 14:59:07 1990
>Method 2: Set Error Code and Return Only at End
My variation on this method (which doesn't require repeated tests) is

	  int function()
          {
	     int success = 0;	/* return status = failure */
	     if (!check1()) {
		if (!check2()) {
		    if (!check3()) {
			success	= 1;
		    }
		}
	     }
             cleanup();
             return success;
          }

which, in your simplified example, could be merged into one statement --
but I presume you're interested in the large-scale structure.
-- 

Steve Emmerson        steve at unidata.ucar.edu        ...!ncar!unidata!steve

============================================================================
>From drh at cs.duke.edu Thu Sep 27 15:33:54 1990
Of the options you present, I would normally use method 3.  Exception
handling is a legitimate use of a goto -- the only legitimate use.  If
I were teaching an undergraduate what to do, though, I would tell them
to use method 2.  Otherwise they would use my suggestion of number 3
as a license to use goto's whereever they wanted.  (I know this is what
would happen from experience.)

There are, of course, other options.  I frequently use a nested if:

int function()
{
  int error;
  if( !(error=check1()) ){
    if( !(error=check2()) ){
      if( !(error=check3()) ){
         /* code */
      }
    }
  }
  cleanup();
  return error;
}

Another possiblity is to use short-circuit evaluation.

int function()
{
  int error;
  if( !(error=check1()) && !(error=check2()) && !(error=check3()) );
  cleanup();
  return error;
}

============================================================================
>From rhl at astro.Princeton.EDU Thu Sep 27 16:37:49 1990
(I've been writing C for 10 years)

I use multiple return's to avoid extra flags; I finmd that it makes for
clearer code. If the cleanup is extensive and non-trivial I usually define

#define RETURN(I) \
   cleanup;
   return(I);
   
   			Robert

============================================================================
>From @po2.andrew.cmu.edu:ghoti+ at andrew.cmu.edu Thu Sep 27 16:53:03 1990
I definitely prefer 1 and/or 2 to 3 -- however I think a lot of it has
to do with the surrounding situation.

For instance - if you are writing a program which is supposed to parse
an input file, and for which any error in parsing the input file could
be considered a critical error, I ususally put together something like
this:

----------------
#define WHERE __FILE__, __LINE__
extern int errno;

void reportError(char *fname, int line, int enum, char *msg)
{
    fprintf(stderr, "<%s:%d> ", fname, line);
    if (enum)  {
        errno = enum;
        perror(msg);
    }
    else  {
        fputs(msg, stderr);
    }
    exit(1);
}

type func(type parm1, type parm2)
{
    int eno;
    
    errno = eno = 0;
    if ((parm1 = some_func(parm2)) == -1) {
        eno = errno;
        reportError(WHERE, eno, "some_func failed.\n");
    }
    return (parm1);
}
----------------

It tends to keep most of the rest of the code cleaner - especially if
each function that tries to perform something that has a chance to fail,
reports it in such a way.  That way less of the other code needs to
actually check for errors.

Of course the above is only for a situation where the errors are all
considered critical.  But one can easily modify the above to understand
keywords (macros, or enums perhaps) like ERROR_CRITICAL,
ERROR_TEMPORARY, ERROR_IGNORE, etc. and only have the program exit on
the first --- or use more than one function for the different types of
errors.

Or you could use the above like a mediator of sorts -- allowing the
called function to produce it's own diagnostic message, and then either
exit blindly or return an status value back to the calling function -
which then can do what it wants, and not have to worry about putting out
a diagnostic message (as it's already been taken care of by the actual
function that had an error)

(I probably have more to blab about, but I have a meeting to go to, and
my mind is getting overloaded -- hope the above is of some use.....)

--fish

============================================================================
>From morse at cs.unc.edu Thu Sep 27 16:57:38 1990
My $0.02:

Option 1:  This works and is clean.  While most people are familiar with the
axiom you refer to, most only look at a part of it.  From what I have read,
every function should have one entry point and one *NORMAL* exit point.  
Additional (possibly multiple) abnormal termination points are permitted.
This is especially true for small functions where the extra "return" is
clearly visible.  If it is nested inside three loops and two conditionals,
you might want to reconsider.  I often code my error checking as return
statements as follows:

	if (precondition 1 not met) return;
	if (precondition 2 not met) return;
	the main body of the function;

I find this very readable.

Option 2: Yuck!!!!  I know this is what blind obedience to programming rules
might dictate, but I find following if-then-elses MUCH more confusing than
multiple exit points.

Option 3: I've never tried this, but it has a certain aesthetic quality.
While "No GOTOs" is true, the scope of this is limited to the function alone
and only under very clearly defined conditions.  I'm not quite sure what
side-effects you might produce.

Here's my single, most overriding, programming maxim:
  Thou shalt do that which is most readable!

If you have to choose between readability and some programming "rule",
choose the readability every time.  You'll never go wrong.

Bryan
morse at cs.unc.edu

============================================================================
>From ddb at ns.network.com Thu Sep 27 17:34:51 1990
:Method 3: Use GOTO to Create Exception Handler
This is a stupid rule, and this sort of error-handling situation is
EXACTLY the situation where use of GOTO can make your code
significantly easier to understand and maintain.  I find Method 2 a
major mistake.  I usually go with method 1, except when there's
standard cleanup work to do in which case I use method 3.
-- 
David Dyer-Bennet, ddb at terrabit.fidonet.org
or ddb at network.com
or ddb at Lynx.MN.Org, ...{amdahl,hpda}!bungia!viper!ddb
or Fidonet 1:282/341.0, (612) 721-8967 9600hst/2400/1200/300

============================================================================
>From ath at prosys.se Fri Sep 28 03:16:15 1990
I don't know about 'the best', but there are some 'reasonably good' wyas.

>Method 1: Return Error Code When Error Is Encountered
Contrary to Software Engineering principles? No big matter. The only
thing that matters is getting it right. It could be difficult to make
fundamental changes to the code later, though.

>Method 2: Set Error Code and Return Only at End
This is close to the method I use in quick-and-dirty code, especially
when cleanup isn't expensive.  The reason: sometimes a routine can do
useful work even *after* an error has occurred (this usually involves
more that one 'error' variable, though.

I prefer to do the cleanup immediately after the error was detected, though:

  ...
  if (!error) {
    error = ...something that may fail and needs cleanup ...
    if (error) {
      ...cleanup...
    }
  }

Then the error variable is used only to bypass the code. If it can take
more than one value, it could also be used for alternative actions:

  if (error == 1) {
    ...handle this case specially...
  } else if (error) {
    ...all other cases are lumped together...
  }

>Method 3: Use GOTO to Create Exception Handler
One question: why is that rule there? If you know, you also know when
goto's are useful.

The code below does not contain any goto's in the regular control
flow. It is not until an error has been detected that one simple and
obvious goto is used to transfer to the cleanup code. In my book, this
is correct programming.

This is actually my preferred way of handling things. I saw it
described in a note in comp.lang.c some years back. It works like
this:

  {
    int state = 0;
    ...compute...
    if (...error...) goto error;

    state = 1;
    ...compute...
    if (...error...) goto error;
 
    ... etc ...

    return OK;

/* ----- clean-up section ---- */   

error: 

    switch (state) {
      case 2:
        /* cleanup after state 2 */
      case 1:
        /* cleanup after state 1 */
      case 0:
        /* cleanup after state 0 */
    }
    return FAIL;

Note that the switch statement does not contain any 'break' - control
enters at the correct state label, and falls through the remaining cases.

---

Apart from that, I also have a penchant for providing clear-text error
messages when any function returns an error.  Something like this:

  extern char *global_errmsg;

  ...

  if (error_detected) {
    global_errmsg = "Error: out of memory";
    return FAIL;
  }

The message would be printed by the routine that gets the FAIL return.
The reason: the routine that detects the error is usually the one that
knows how to describe it. Rather than leving it to the calling routine
to produce a intelligible message from an error return code, it's
better to just signal the error by FAIL, and the leave it up to the
caller to retrieve the error message.

Hope this is of any use,

-- 
Anders Thulin       ath at prosys.se   {uunet,mcsun}!sunic!prosys!ath
Telesoft Europe AB, Teknikringen 2B, S-583 30 Linkoping, Sweden

============================================================================
>From kevin at math.lsa.umich.edu Fri Sep 28 11:48:43 1990
How about

int
error_handler()
{
	int error = NOERROR;

	if (error =  catch1()
	 || error = catch2()
	 || error = catch3())
		;
	cleanup();
	return error;
}

The ||'s allow you to stop as soon as you detect an error.
If you decide later that there is special cleanup to do in the
case of errors, you can make the body of the  'if' do something,
and you stil have only one entry and exit point.
	Best,
	Kevin Coombes <krc at mayme.umd.edu>

============================================================================
>From taumet!taumet!steve at uunet.UU.NET Fri Sep 28 12:23:09 1990
>Method 1: Return Error Code When Error Is Encountered

The above function has one entry point (the opening left brace) and one
exit point (the closing right brace).  That is what is meant by that
principle.  FORTRAN and some other languages allowed a function to
be entered in the middle.  In assembly language you can insert a
return instruction in the middle, which makes maintenance harder, as
function epilogue code might be skipped accidently.  The return statement
in C does not have this problem.

>Method 2: Set Error Code and Return Only at End

You can use nested if's instead:
	if( ! (error = check1()) )
	    if( ! (error = check2()) )
		error = check3();
	cleanup();
	return error;

>Method 3: Use GOTO to Create Exception Handler

There is no rule that says absolutely positively no goto's.  It is just
that most progams when carefully designed and clearly written happen not
to contain any goto's -- they are seldom necessary.
See ACM Computing Surveys, vol 6 no 4, December 1974,
expecially the articles by Knuth and by Wirth.

Your example is too restricted to show any real style differences.

If we assume a more typical model where processing is aborted and an
error code returned on error, processing continuing otherwise,
we can get two styles of interest:

C programmers generally write like this:

	if( check1() )
	    return ERR1;

	process1();
	if( check2() )
	    return ERR2;

	process2();
	if( check3() )
	    return ERR3;

	process3();
	return OK;

Pascal programmers generally write like this (but still using C):

	int result;	/* this should really be initialized to something */
	if( check1() )
	    result = ERR1;
	else {
	    process1();
	    if( check2() )
		result = ERR2;
	    else {
		if( check3() )
		    result = ERR3;
		else {
		    process3();
		    result = OK;
		}
	    }
	}
	return result;

The code is equivalent, and may well result in identical object code,
depending on the compiler.  I don't see that it matters which style
you use, but consistency in style is a big help to anyone reading
your code.
-- 

Steve Clamage, TauMetric Corp, steve at taumet.com

============================================================================
>From tetrauk!rick at relay.EU.net Fri Sep 28 15:16:29 1990
Given your examples, I would say either use style 3, or if the number of
possible errors is only 1 or 2, something like:

	  int function()
	  {
	     int error;
             if (!(error = check1()))
	     {
		     error = check2();
	     }
	     cleanup();
	     return error;
          }

You can't do this too many times, otherwise the nesting gets totally out of
order.

I do not subscribe to the anti-goto religion.  Goto's have a value if used
sensibly, and this is one case where it is sensible.  A general maxim I would
apply is "goto forward is OK, goto back is not".  In other words, never use
goto to implement a loop.

Software engineers don't really say "never use them" anyway - one proof is to
observe that they will discuss in depth the semantics etc. of setjmp/longjmp.
This means they consider it important, and setjmp/longjmp is really a Mega-Goto!

---
Rick Jones			The definition of atomic:
Tetra Ltd.				from the Greek meaning "indivisible"
Maidenhead, Berks, UK		So what is:
rick at tetrauk.uucp			an atomic explosion?

============================================================================
>From karl at IMA.ISC.COM Fri Sep 28 16:31:23 1990
I would use Method 3.

>Of course this breaks the golden rule of software engineering of "absolutely
>positively no GOTO's in your program."

That's a misquote.  Don't you ever use a GOTO when programming in old Fortran,
or minimal BASIC?  A better form of the rule is "use a GOTO only to emulate a
more structured construct which happens to be missing from the language you're
using."

Now, let's look at your description again:

>Method 3: Use GOTO to Create Exception Handler

C doesn't have builtin exception handlers.  Thus, this use of GOTO is well
within the rules, and in fact is *more* structured than some other methods
that do not use GOTO.

Karl W. Z. Heuer (karl at kelp.ima.isc.com or ima!kelp!karl), The Walking Lint
(Permission granted to repost in a summary.)

============================================================================
>From bjornmu at idt.unit.no Fri Sep 28 16:46:51 1990
I have used a method like your #3.  I define this macro:

---------------------------
             /* This macro is used to return an error code from a
		function.  There must be a local integer variable
		Error, initialized to 0, and a label Return at the
		end of the function, where clean-up is performed.
		The function must end with "return Error".    */

#define Ret(err) { Error = (err);  if (Error)\
		   { fprintf (stderr,\
			      "File: %s\tLine:%d\n", __FILE__, __LINE__);\
		     TrapError (Error); }\
		   goto Return; }
----------------------------

Error could also be a global variable, maybe that would have been
better.

I call this macro whenever an error is found:

  if (! some_pointer)    Ret(BAD_POINTER);

, and also to return before the end of the function:

  if (finished_early)   Ret(0);

TrapError prints a descriptive text to stderr.

If the error is of a serious kind (not just a user mistake), TrapError
does something that "offends" the debugger, so that it stops.

The latter, plus the printf statement, should of course be removed in
a finished product.

With a few exceptions, my functions return 0 upon success, otherwise
an error code, which I always check for, like this:

  int  retcode;

  ....
  retcode = SomeFunc (param);
  if (retcode)  Ret(retcode);

Bj|rn Munch                  | Div. of Comp. Science & Telematics,
bjornmu at idt.unit.no          | Norwegian Institute of Technology (NTH),
PhD Student (well, soon...)  | Trondheim, Norway
 (some filler words here)    | You can finger me @garm.idt.unit.no

============================================================================
>From geovision!pt at uunet.UU.NET Sat Sep 29 00:20:08 1990

Here, we almost always use the 3rd method.  Our code looks a lot like

...
	if (!dbi_act_query(args, &qid))
		goto err_exit;

	if (!dbi_get_1_row(qid))
		goto err_exit;

...
	return TRUE;
err_exit:
	...cleanup code..
	return FALSE;

We also issue error messages at the earliest possible opportunity, which
some times mean you get cascading messages like:

ipc_tcpip_spawn: permission denied.
ipc_startup: Could not establish link to program "gfx_dm" on node: "zaphod"
gfx_init: Graphics Display manager not started.
plot_open_dev: Unable to open device "X Window"

which can get kind of confusing.  Some of our newer code passes back more
status codes, so that higher level code can issue message that are more
meaningful than just success or failure.
---
Paul Tomblin, Department of Redundancy Department.    ! My employer probably 
I'm not fat..... I'm metabolically challenged.        ! does not agree with my
I'm not underpaid... I'm financially challenged.      ! opinions.... 
nrcaer!cognos!geovision!pt or uunet!geovision!pt      ! Me neither.

============================================================================
>From sactoh0!pacbell!jak at PacBell.COM Sat Sep 29 14:58:36 1990
>Method 1: Return Error Code When Error Is Encountered
>
It depends on the function's complexity. Generally though I don't 
like to do this though. It can make tracing the code harder. However,
it is probably one of the cleaner ways to do it.

>Method 2: Set Error Code and Return Only at End

This is probably the most "structured" way to do it. But it makes for
more complecated code. Still, I don't think I would code like this.

>Method 3: Use GOTO to Create Exception Handler

Well, golden rule or no, this _will_ be the fastest meathod available.
This is the meathod I would use. However, be careful to structure it
carefully and DO NOT OVERUSE the longjump. Also, avoid jumping out of
a function. That could cause more problems than it solves.

The problem with using jumps/goto's is that they are so easy to misuse
and make your program unreadable. However, using them for error conditions
is a generally acceptable.

>
>Thanks for your time,
>
Hope I was of help.

-- 
-------------------------------------------------------------
Jay @ SAC-UNIX, Sacramento, Ca.   UUCP=...pacbell!sactoh0!jak
If something is worth doing, it's worth doing correctly.

============================================================================
>From naitc!cookr at uunet.UU.NET Mon Oct  1 11:21:40 1990
Concerning the testing/returning of error codes, I've found that academia
is a bit out of touch when it comes to multiple return points.  As you
point out, it's a pain to have to check for previous errors before taking
the next step.  By the same token, using a GOTO for exception
handling/cleanup is also poor form.  Yet, what's so terrible about an
if-then block which quite clearly is cleaning up from an error and returning
to the caller?  In my experience, this form is the most clearly understood
and easiest to maintain.

As an aside, one form which was pseudo-missed is somewhat similar to the
test-for-previous-error one.  Specifically, rather than repeatedly testing
an error-state variable, you simply use nested if-thens.  Of course, for me,
this is the absolute WORST method.  I say this because I used to work with
a person which did this and 12 levels of nesting weren't all that unusual.



More information about the Comp.lang.c mailing list