41

At this point in my programming experience, I realize how spoiled I am to have exception handling available in most languages being used today (C++, .Net, Java, etc), at least in comparison to C. I am getting ready to take an advanced C course and has me really thinking those terms in comparison to my current paradigm.

In C, it's up to the programmer to prevent errors from ever occuring in the first place, which is quite daunting for anybody who is used to exception handling. It has occured to me that any language that I have come across that has exception handling happens to be object oriented. The first object oriented language to have exception handling, at least to my knowledge, is C++ which is sort of an evolution of C. (please correct me if I am wrong)

With that said, is there something about the object oriented nature of a language that allows exception handling, or was exception handling added as a feature as object oriented languages really started becoming a commonplace? What is that C lacks that to say, C++, in machine code that makes excpetion work?

I found this post about how exception handling works under the hood, but not sure how that information applies to my question (ie, does C lack notifications, continuations, etc?). Thanks in advance.

Community
  • 1
  • 1
Chad Harrison
  • 2,836
  • 4
  • 33
  • 50
  • The wikipedia entry on Exception handling (http://en.wikipedia.org/wiki/Exception_handling) is a good starting point to learn about this subject. – Daniel Daranas Dec 20 '11 at 18:03
  • 1
    None of these exist, C is all that exists. Exception handling is just an abstraction. PS: Do you know about setjmp/longjmp? – sidyll Dec 20 '11 at 18:05
  • You can [implement exception handing in C](http://en.wikipedia.org/wiki/Setjmp.h#Exception_handling) using `setjmp()` (roughly `try`) and `longjmp()` (roughly `throw`). There's nothing that C "lacks" in this area. – Frédéric Hamidi Dec 20 '11 at 18:06
  • http://www.di.unipi.it/~nids/docs/longjump_try_trow_catch.html – Flexo Dec 20 '11 at 18:07
  • What exactly do you mean by "machine code"? Do you know, that "machine code" depends on architecture, not language...? – Griwes Dec 20 '11 at 18:08
  • As far as I've heard, object oriented languages allow stack-unwinding, which makes generic exception handling possible. – Mooing Duck Dec 20 '11 at 18:11
  • This question is currently tagged `c++` and `c`. Are you specifically asking about C++? – Aaron McDaid Dec 20 '11 at 18:13
  • 2
    I think your premise is a bit flawed. Standard ML has (by most definitions) exception-handling but not (by most definitions) OOP; likewise Ada before Ada 95. How many non-object-oriented languages have you used? – ruakh Dec 20 '11 at 18:14
  • 5
    @MooingDuck -- OO languages don't "allow" stack unwinding any more than a potato allows cooking. Most OO languages *require* stack unwinding for one reason or another, and that facilitates implementing EH. But stack unwinding capability (ie, the ability to examine a call stack and discern the individual procedure boundaries) is a feature of the language support environment that may be present for other reasons (such as debugging, support of procedure scoping, etc). – Hot Licks Dec 20 '11 at 18:30
  • When I say "machine code", I mean compiled code. But looking at the answers given, machine code portion doesn't really apply because I realized it was a higher level implementation problem. I was gearing the questions C to and C++ because of how the language was transformed and the times in which they were used, and they are compiliable languages. @ruakh A few other interpreted languages (a few different flavors of BASIC) and one obsure language called PowerOn similar to PL/1. – Chad Harrison Dec 20 '11 at 19:06
  • 3
    @HotLicks: I like the potato analogy. More generally, OO languages do not require stack unwinding for exception handling; *any* language that has any kind of control flow requires that the *continuation* -- the "what am I going to do next" data structure -- be amenable to inspection and alteration. In many OO languages that data structure happens to be a stack, and therefore exception handling requires inspection of the stack. But there is nothing about OO languages that requires that a stack be used for the reification of continuation! OO languages can be stackless. – Eric Lippert Dec 20 '11 at 19:07
  • @EricLippert -- But if not properly stacked then all the objects fall on the floor. ;) – Hot Licks Dec 20 '11 at 20:05

9 Answers9

20

C lacks nothing in machine code, and exception handling was and is commonplace in C with setjmp and longjmp.

The reason for the complete lack of a language-level feature in purely procedural languages is that exception handling is identical to setjmp when no destructors need to be called. Exception handling has been around before in exotic languages, but never caught on because it was purely syntactic sugar. However, once destructors entered the scene and stack unwinding became necessary, language-level support became necessary and exception handling was widely implemented as part of the language.

Mooing Duck
  • 64,318
  • 19
  • 100
  • 158
thiton
  • 35,651
  • 4
  • 70
  • 100
  • 2
    I'm curious: how do C-style exceptions deal with cleaning up intermediary allocations? Even without full-fledged destructors, somebody's got to `free` that memory, right? Is it simply the responsibility of the calling function to ensure that allocations are not `longjmp`'d around? – Nicol Bolas Dec 20 '11 at 18:30
  • @NicolBolas: It is. Simple, stupid, and one of the reasons people try to avoid `longjmp` altogether and handle errors in the return value channel when dynamic allocations are made. I've seen documentation that marked some functions as `longjmp`-safe and some others as not safe. OTOH, I've also seen enough people declaring that `longjmp` should be used for exceptions only and that the loss of a few KBs of memory is no issue during exceptions. – thiton Dec 20 '11 at 18:34
  • 2
    @Nicol: you can think of exceptions as being "purely syntactic sugar" for a combination of setjmp/longjmmp with so-called "exception frames". In C, you could create a per-thread linked list of structures. Whenever you hold some resource that needs freeing, and you want to call something that could "throw", you add a structure to the list, call the thing, then unlink the structure. When something "throws", it `longjmps` to whatever is at the end of the list, and that thing can free up the resources at that level and then `longjmp` to the next thing on the list... – Steve Jessop Dec 20 '11 at 18:41
  • 1
    ... To "catch", you lay down an exception frame that when longjmped to, doesn't continue up the list but instead resumes execution at this level. That's probably the easiest way to implement exception-handling for yourself in C, if not the most efficient. – Steve Jessop Dec 20 '11 at 18:44
  • 7
    The way exception handling is handled in most C++ compilers, where the cost is at the throw side, cannot be mimicked using setjmp and longjmp. IN particular, calling setjmp forces a cost upon every `try` block even if an exception is not thrown. GCC in particular has an essentially zero-cost `try` at the cost of an expensive `throw`. I do not believe you can duplicate this in C at the language level. – edA-qa mort-ora-y Dec 20 '11 at 18:49
  • @edA-qamort-ora-y: While you are right for naive implementations of setjmp, this is purely a performance consideration and does not affect program outputs. Compiler creators could even choose to optimize setjmp/longjmp in the same way as exceptions (breaking ABI compatibility, just like Dwarf2 does). – thiton Dec 20 '11 at 18:54
  • "exception handling is identical to setjmp when no destructors need to be called" - cleanup code doesn't have to be destructors - it could be undoing anything that was done before / at the beginning of the try block. That C++ makes you wrap it in a class and put the cleanup code in the destructor doesn't mean that a "finally" mechanism is fundamentally OO. (And this is ignoring the fact that you don't need to have cleanup code to benefit from a structured way to store information about the cause of the error - and if that's an object, then so's an int, since it could be an int.) – Random832 Dec 20 '11 at 20:28
  • @edA-qamort-ora-y But, gcc does effectively duplicate this "in C, at the language level": via __cxa_throw / __cxa_begin_catch, and the like. The ability to write `try{}catch(){}` is very nice sugar, but a determined and eccentric C programmer could use those routines, as well. `nm -D /usr/lib65/libstdc++.so.6.0.16|grep __cxa` e.g. – BRPocock Dec 20 '11 at 20:53
  • Not sure I agree with this answer. Exceptions are more than just syntactic-sugar for `setjmp`/`longjmp` (even if the resulting assembly were exactly the same): they logically *mean* different things. When I see `setjmp`/`longjmp`, I think *"the coder wanted to jump into another piece of code, and for some reason didn't/couldn't refactor to use functions."* When I see an exception thrown, I think *"an error has occurred, and we want the caller to decide what to do about that."* – BlueRaja - Danny Pflughoeft Dec 20 '11 at 22:45
  • 2
    @BlueRaja-DannyPflughoeft: at this point we're dangerously close to "structured programming is/isn't syntactic sugar for `goto`" ;-) – Steve Jessop Dec 20 '11 at 23:20
15

Does exception handling require object-oriented programming?

No. The two are completely separate. One can have OO languages that do not have exception handling as a control flow primitive, and one can have exception handling in non-OO languages.

Object-oriented programming, as Wikipedia helpfully points out, is a style of programming that emphasizes the value of abstraction, encapsulation, messaging, modularity, polymorphism, and inheritance in order to achieve low-cost code re-use and effective management of complex software projects implemented by large teams.

You don't see "loops" or "if statements" or "goto" or "try-catch-finally-throw" on that list because control flow primitives have nothing whatsoever to do with abstraction, encapsulation, messaging, modularity, polymorphism or inheritance being used to achieve low cost code reuse or effective management of complex software projects by large teams.

What is that C lacks that to say, C++, in machine code that makes exceptions work?

It is certainly the case that modern hardware is designed with exception handling as a control flow primitive in mind. C was designed long before that modern hardware existed, which would make it somewhat more difficult to implement exception handling in C that runs efficiently on all the hardware that C runs on.

But that said, there's nothing stopping you or anyone else from designing a new version of C that has exception handling as a control flow primitive, but without all the other features of C++.

If you're interested in the subject of how to add exception handling to non-OO languages that support continuations, see my article on the subject that sketches out the idea:

http://blogs.msdn.com/b/ericlippert/archive/2010/10/22/continuation-passing-style-revisited-part-two-handwaving-about-control-flow.aspx

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
10

With that said, is there something about the object oriented nature of a language that allows exception handling, or was exception handling added as a feature as object oriented languages really started becoming a commonplace?

I first got to know exceptions when I had to learn Ada (studying CS) in the early 90s. IIRC, Ada had a special type Exception. It was, back then, not an object-oriented language. (Ada95 added some OO concepts.) However, I agree that stack unwinding (i.e. the complete automatic cleanup of allocated resources) is an important trait for the success of exception handling. Combining destructors with exception handling is an important point for the success of exceptions in C++.

I also seem to remember Stroustrup mentioning Ada as a major influence for exception handling in C++.

sbi
  • 219,715
  • 46
  • 258
  • 445
  • 1
    "stack unwinding is an important trait for the success of exception handling". In C++, that is. In Java you don't need stack unwinding, because you have GC instead. – Steve Jessop Dec 20 '11 at 18:15
  • +1 Right -- Stack unwinding was probably THE critical piece. Without stack unwinding you just had setjmp/longjmp, with no scoping of data or control, and exception handling was largely unmanageable, except as a global "panic" mechanism. With stack unwinding EH was pretty much "just a simple matter of coding". Whether stack unwinding was possible or not depended on the support environment for the language, and the call/return protocols involved. – Hot Licks Dec 20 '11 at 18:17
  • 6
    @Steve: GC only deals with a single resource. It doesn't close your files and it doesn't release your mutexes. AFAIK you have to explicitly do this using `finally`. I consider this a regression from C++. – sbi Dec 20 '11 at 18:18
  • @SteveJessop -- Sorry, that's total BS. Stack unwinding is just as critical in Java as in C++. – Hot Licks Dec 20 '11 at 18:18
  • @sbi: whether that's a regression or not, it's no barrier at all to the existence of exceptions in Java. Exceptions as a language feature need some kind of resource cleanup, they don't need specifically the same kind of resource cleanup as C++. Without formal stack unwinding, though, the programmer ends up wanting `finally` instead, which does a similar job in a slightly different way. – Steve Jessop Dec 20 '11 at 18:27
  • @HotLicks: "Sorry", stack unwinding in C++ is the execution of destructors for objects with scopes that are exited between the throw point and the catch point (15.2/3). Java doesn't have destructors, and can be implemented entirely without them, by transferring control from the throw site to the correct catch or finally site. If you think that using C++ terminology is "total BS", well, don't jump into discussions that involve C++. – Steve Jessop Dec 20 '11 at 18:34
  • @Steve: So how are files automatically closed, and mutexes released, in Java, when an exception is thrown? Doesn't it need the explicit use of `finally`? – sbi Dec 20 '11 at 18:38
  • @SteveJessop -- The actual act of "unwinding" is only a small part of subject of "stack unwinding". The ability to introspect the stack (reliably) is the critical piece. Many older runtime environments did not support this introspection, even when they (apparently) allowed inspection of the stack for debug, etc. And, interestingly, RISC-like architectures made this introspection more critical, since registers needed to be restored even in a C setjmp/longjmp implementation. – Hot Licks Dec 20 '11 at 18:38
  • @SteveJessop: I disagree with your analysis. In C++, "stack unwinding" is the execution of destructors, because that's what C++ does; in Java, the corresponding action is the execution of `finally` blocks, and it involves exactly the same sort of stack-examination process. You don't have to call that "stack unwinding" if you don't want to, but it's absurd to then say that "in Java you don't need stack unwinding" when Java is doing the same thing under a different name. – ruakh Dec 20 '11 at 18:41
  • @ruakh: It's not absurd, because I happen to know that sbi understands C++ well, and I expected that he would understand the C++-defined meanings of terms that he has used in his own answer whilst referring to C++. If anything, Java isn't doing the same thing under a different name, it's doing a *different* thing (executing `finally` clauses) under the *same* name. I should probably have said "GC *and finally* instead", though, not just "GC instead". The big difference is just that dtors associate the cleanup with the object itself, whereas `finally` associates it with that particular use. – Steve Jessop Dec 20 '11 at 18:48
  • @sbi: Depends what you mean by "releases your mutexes". If you're using some object representing an inter-process mutex, you're on your own. But most Java synchronization is done with the `synchronized` keyword, and the reason that's scoped is so that exceptions do release mutexes. – Steve Jessop Dec 20 '11 at 18:56
  • @SteveJessop: Hmm. O.K., I take back the "absurd", but I still disagree with it. sbi defined "stack unwinding" as "the complete automatic cleanup of allocated resources"; in C++ that's handled via destructors and `auto` storage, in Java via `finally` blocks (plus special cases like scoped `synchronized` blocks, as you mention). But the "automatic" part of "automatic cleanup" is sufficiently different between the two languages that I'll accept your quibble as valid, even if I disagree with it. :-P – ruakh Dec 20 '11 at 18:57
  • @SteveJessop -- And again, you clearly do not fully understand the issues involved. How many exception handling systems have you written? – Hot Licks Dec 20 '11 at 18:58
  • @HotLicks: alone, none. In collaboration, I have only implemented one JVM. And again, if "understanding the issues" means "not describing Java using C++ terminology when talking to someone with whom I normally talk about C++", then I do not understand the issues. If it means something more worthwhile, you probably don't know what I understand or don't understand. – Steve Jessop Dec 20 '11 at 19:25
  • @ruakh: OK, fair enough, obviously I shouldn't have focussed on using the C++ definition of "stack unwinding", since it's provoked both your disagreement and whatever HotLicks thinks is so terribly important. – Steve Jessop Dec 20 '11 at 19:39
  • It's maybe misleading to call exceptions a special type. When you `raise` an Ada exception, you're not throwing an object instance. Exceptions are named, and IIRC extra details can be included (meaning you can get similar functionality to throwing a class instance with member fields), but I don't recall anyone equating that with anything like the C++ model of throwing an instance object. It was a long time ago for me, but the way I remember it, Ada exceptions are "messages", and often compared/contrasted with functions, rendezvous and goto, but not seen as thrown data values. –  Dec 20 '11 at 23:22
  • Also worth mentioning - exceptions were in Ada from the start (the 1983 standard at least), whereas "true OOP" wasn't added until the 1995 standard - a feature of tagged types that supports inheritance plus some single-dispatch late-binding support. –  Dec 20 '11 at 23:29
  • @Steve314: I didn't try to equate Ada's exceptions with those in C++. I really don't know anymore what they were like in the type system. I have forgotten most about Ada's exceptions (it's 20 years ago, after all), but I specifically remember my mind being blown by the freedom to throw anything in C++, because I only knew Ada's exceptions then. BTW, I did mention OO only coming into the language with Ada95. – sbi Dec 21 '11 at 08:20
7

Does exception handling require object-oriented programming?

No. The two are orthogonal. Others mentioned the setjmp and longjmp used in C to handle errors. I want to mention SEH.

SEH (structured exception handling) is a microsoft extension to C with an OS-level support. It lets you write code like (example from MSDN):

__try 
{ 
    *pResult = dividend / divisor; 
} 
__except(GetExceptionCode() == EXCEPTION_INT_DIVIDE_BY_ZERO ? 
         EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH)
{ 
    // handle exception
}

You can raise your own exceptions too by calling RaiseException. Unlike setjmp and longjmp you can do your own cleanup in __finally blocks. In fact C++ exceptions are implemented on top of SEH (on windows).

This is an example of exception handling in totally object non-oriented way.

Another example in C++ that doesn't use any object oriented features:

try {
    throw "Boom!";
} catch(const char* str) {
    printf("Error: %s\n", str);
}
Yakov Galka
  • 70,775
  • 16
  • 139
  • 220
  • @Steve314: I don't think this is true. You need to access function's stack frame from inside except/finally to do anything useful, thus you need either compiler support or write everything in assembly (including the function body). – Yakov Galka Nov 20 '13 at 06:26
  • Having done some more reading, I now believe (but still don't know) that the language extension and API for SEH were written together as a unit. Given what [the article I linked earlier says](http://www.microsoft.com/msj/0197/Exception/Exception.aspx), it seems unlikely that the OS service could have been designed or used separately from the language extension. –  Nov 23 '13 at 09:41
5

Non-object-oriented languages that implement exception handling include:

  • ITS TECO
  • PL/1 (Multics)
  • C (as several people have pointed out, via setjmp/longjmp)
  • C by way of Unix signals: Unix kernel signals are derived from the Multics exception-handling facility
  • older versions of Lisp (granted, Common Lisp allows OOP, but it didn't when conditions and restarts were added) which apparently implemented conditions and restarts (unwind-protects) from ITS TECO — according to RMS (http://www.gsim.aoyama.ac.jp/~ida/GNU/RMStalk1207.html), implying that Lisp actually inherited exception handling by way of Emacs (nifty!)
BRPocock
  • 13,638
  • 3
  • 31
  • 50
  • I was reading about signals, but one source mentions signals was not intended for exception handling. http://en.wikibooks.org/wiki/C_Programming/Error_handling – Chad Harrison Dec 20 '11 at 19:33
  • It's a process-level analogue. Arguably both similar and different. One *could* potentially wrap a block of code by setting a `SIGFPE` handler with `signal`, in much the same way as a `try {} catch (floating-point-error e) {}`. http://en.wikipedia.org/wiki/Unix_signal#Relationship_with_Hardware_Exceptions seems to share the idea of the analogy. – BRPocock Dec 20 '11 at 19:43
  • It is a stretch to argue that setjmp/longjmp, and, even more so, Unix signals are "exception mechanisms", in the meaning of the term even by the time of Ada. Rather, Ada-style exception handling was in large part a reaction to/against schemes like Unix signals which were totally unstructured. – Hot Licks Dec 20 '11 at 20:09
  • They all implement non-local transfer of control to a pre-designated entry point specifically designed to handle a type of unusual circumstance that interrupts the normal flow of code. As far as I can see, the rest is syntactic sugar on top of the basic concept. – BRPocock Dec 20 '11 at 20:17
  • Well, if you consider it to be "syntactic sugar" that with exception handling the error handling code is inside the function that set up the exception handler and therefore has access to all [at least, all declared outside the scope of the try block] its local variables (i.e. the state that needs to be cleaned up), without forcing you to move that state to somewhere other than the stack. – Random832 Dec 20 '11 at 20:38
  • It's extremely nice syntactic sugar :-D but, after all, C++ exceptions (in particular) are usually compiled down to C constructs in their AST form… I'm extraordinarily fond of the restart system in Lisp, actually, which adds many things to that model (code running more or less in both the caller and callee's context), but it's still catching a processor interrupt or doing something like a `longjmp` at the low level. – BRPocock Dec 20 '11 at 20:40
4

For a non-imperative example, try Haskell on for size. Exceptions don’t even need to be built into the language; they’re just part of the return type (e.g., Either MyException MyValue, or ExceptionalT IOException IO String). Standard exceptions can be handled with the try function from the Control.Exception module:

main = do
  result <- try (evaluate (1 `div` 0))
  case result of
    Left  exception -> putStrLn $ "Caught: " ++ show exception
    Right value     -> putStrLn $ "Result: " ++ show value

You can also use a function as an exception handler with the catch function, here used infix:

main = (print $ 1 `div` 0) `catch` \exception ->
  putStrLn $ "Caught: " ++ show exception

And you can use the Exception monad to carry out exception-throwing operations in another monad. By handling all possible exceptions, you escape from the Exception monad.

main =
   do result <- runExceptionalT someFunction
      case result of
         Exception exception -> putStrLn ("Caught: " ++ show exception)
         Success   value     -> putStrLn ("Result: " ++ show value)

Because an exception forms part of a function’s type signature, it must be explicitly accounted for. This is essentially the same thing as checked exceptions in Java.

Jon Purdy
  • 53,300
  • 8
  • 96
  • 166
  • Not sure if you mean to, but you make it look a bit as if merely using return type `Either` is already exception handling, which it isn't. – leftaroundabout Dec 21 '11 at 05:20
  • @leftaroundabout: That was unintentional. I added a bit more…though I’m not sure whether it’ll be any clearer to someone who’s unfamiliar with Haskell. – Jon Purdy Dec 22 '11 at 01:50
1

Well, exceptions are found within assembly language where you can use traps (forced exception) and other exceptions to control the flow of the program. An example would be a nullpointer or why not stackoverflow. There is nothing about the nature of OO-languages that enables exception handling. They just make it easier (and high-level languages have a tendency to throw a lot more exceptions). Exceptions is a key feature in basic programming. And by that I don't mean all programming, I mean "regular" programming, including assembly. I don't understand what you mean when you say "what is it that C lacks that to say, C++, in machine code that makes exceptions work?". And I wouldn't say that it is completely up to the programmer to catch exceptions in C (but I'm a novice in this area. If anyone can correct me, please do).

keyser
  • 18,829
  • 16
  • 59
  • 101
1

Exception handling has been around for a fairly long time, well before C++. Several "boutique" languages implemented exception handling fairly early, but Ada (late 70s, IIRC) was probably the best known. Ada had glimmers of OO-ness, but was not OO by any modern standard.

Exception handling was also implemented in several versions of the PL/S languages (definitely not OO) that were mostly used internally to IBM. Early implementations (going back to the late 70s) were developed using macros (the PS/S macro processor was superior to most since), but later versions embedded EH into the language.

Hot Licks
  • 47,103
  • 17
  • 93
  • 151
1

Non-OO languages also have exceptions, it's a useful abstraction for unwinding the call stack. Examples are Erlang, and Forth.

Paul Rubel
  • 26,632
  • 7
  • 60
  • 80