7

I'm working on a large C application that I've divided into sub-libraries (e.g. networking, message handling, message building, etc.)

I've converged onto a pattern where functions are only allowed to return error codes, and all data must be returned via supplied pointers. This pattern has worked well because it is very easy to "bubble-up" errors that are generated from low-level.

I'm curious, are there other standard design patterns for dealing with errors in large C programs?

1

2 Answers 2

6

There are errors, and there are errors.

Programming-errors are either handled by assertions, which only fire in debug-mode, saving release-mode from the associated costs, or by immediate abort with an error-message. Trying to recover risks more damage to the users data and is thus generally inacceptable.

The possibility of outside interference (radiation, wear and tear on the computing system) argues for more checking of the internal consistency left in the release version, and using a supervisor process to restart if needed.

Somewhat expected outside errors, like insufficient resources, needed resources denied (cannot gain sufficient access), input errors, and errors from peers, servers, and clients on the other hand generally have to be handled gracefully. The main ways for doing that are:

  1. Handle it yourself. For example retry, wait, or try alternative. Often cannot be done, and certainly degrades the control the caller has.

  2. Notify the caller. Let him handle that in any appropriate way.

    1. If the return-value has some invalid states (often null-pointer, negative integers, and the like) use one of those, and optionally store additional error-info elsewhere (see errno).
    2. Return an error-code, data has to be returned through out or in-out parameters. Might be a bit more cumbersome, but can be applied uniformly.
    3. Return a structure combining error-code and return-data. Especially popular in functional languages, and gained more popularity with Rust and other languages eschewing exceptions.
    4. Use exceptions. Not directly applicable to C, though take a look at setjmp and longjmp. It has been done before, for example in the implementation of the Lua interpreter.
    5. Use a callback if an error is encountered. This is also often used for periodic status-information in a long-running possibly non-asynchronous operation to allow feedback to the user. The infamous Annex K goes that route. And one of the important criticisms is the use of the callback, especially that it isn't even thread-local, not being passed every call is somewhat easier to digest.
2
  • All true, maybe helpful for the OP, but not really an answer to the question from the title, I think.
    – Doc Brown
    Commented Jun 22, 2021 at 15:20
  • @DocBrown Paraphrased: There are multiple (more or less) standard patterns, appropriate in different circumstances, and here they are. Which implicitly answers the question whether there is one with no, multiple. Also, the question in the body asks explicitly for that. Commented Jun 22, 2021 at 15:54
0

Depending on your usecase it may be beneficial to use assertions wherever you are absolutely 100% sure that further execution of the program makes no sense or is dangerous. They can be used as a way to immediately tell you if things have gone bad, where they have gone bad, and give you first hints on where to start looking for the underlying error.

People make the plausible point that assertions should never end up in production code, and I would only advise them in the developement/testing phase. You can then omit them via compiler flag for your production code.

1
  • 4
    Using assertions is more debugging than error handling.
    – Doc Brown
    Commented Jun 22, 2021 at 14:29

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.