Path: utzoo!attcan!uunet!wuarchive!zaphod.mps.ohio-state.edu!uakari.primate.wisc.edu!aplcen!haven!cs.wvu.wvnet.edu!cerc.wvu.wvnet.edu!blackwater!vrm From: vrm@blackwater.cerc.wvu.wvnet.edu (Vasile R. Montan) Newsgroups: comp.lang.c Subject: Re: Error Handling Summary: Summary of replies recieved by email Keywords: Errors Message-ID: <849@babcock.cerc.wvu.wvnet.edu> Date: 1 Oct 90 21:17:37 GMT References: <837@babcock.cerc.wvu.wvnet.edu> Sender: news@cerc.wvu.wvnet.edu Lines: 753 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@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@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@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@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@cray.com ============================================================================ From steve@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@unidata.ucar.edu ...!ncar!unidata!steve ============================================================================ From drh@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@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+@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@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@cs.unc.edu ============================================================================ From ddb@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@terrabit.fidonet.org or ddb@network.com or ddb@Lynx.MN.Org, ...{amdahl,hpda}!bungia!viper!ddb or Fidonet 1:282/341.0, (612) 721-8967 9600hst/2400/1200/300 ============================================================================ From ath@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@prosys.se {uunet,mcsun}!sunic!prosys!ath Telesoft Europe AB, Teknikringen 2B, S-583 30 Linkoping, Sweden ============================================================================ From kevin@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 ============================================================================ From taumet!taumet!steve@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@taumet.com ============================================================================ From tetrauk!rick@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@tetrauk.uucp an atomic explosion? ============================================================================ From karl@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@kelp.ima.isc.com or ima!kelp!karl), The Walking Lint (Permission granted to repost in a summary.) ============================================================================ From bjornmu@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@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@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@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@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.