Exceptions, Go to Hell!

I

Ian Collins

I am currently developing a container library in C. Since C doesn't
support exceptions, maybe people here can be interested in a different
approach to exceptions and error handling.

The basic design is that each container function (method in the C++
terminology) calls a function if an error occurs. This function call is
the result of a lookup of several "stages":

(1) Each container contains a special "ErrorFunction" field that can be
set by the user after the container is created. This field contains a
function pointer to the function that will be called in case of an
error. This field is initialized by default to the default error
function by the creation procedure

One problem I see with this approach is the error functions is like a
signal handler, it gets called out of the normal flow of execution. So
there isn't a lot it can do. The user still has to check the return
code from the operation.
(2) The library has a default error interface for errors that happen
during the creation function. This default error interface can also be
changed dynamically by the user at any time.

If the function call returns, the container returns an error code in
most cases or NULL if there is a single return value.

To obtain a behavior similar to C++ exceptions it suffices to set a jump
context with setjmp and store it in some global. If an error occurs, the
error function just jumps into the recovery context.

The risk there is you don't have any automatic clean up in C, so writing
"exception safe" code is really difficult.
 
J

joe

jacob said:
Le 29/08/10 06:24, joe a écrit :

Each container has a pointer, not each element in the container.

Duh, don't try to downplay it. A pointer per container for non mainline
purposes is too big a pill to swallow. Other people may like your design.
I don't. It's all good, right?
The
cost of that pointer will be amortized by the number of elements that
the container holds. > This is a flexible approach that allows for a
fine control of the behavior in case of an exception.

No need to hard-sell me, I'm not buying. No offense. I'll roll my own (as
if I haven't already).
If we would store a pointer to the error function for all containers,
it would be too coarse for most applications.

"we"? Surely you jest.
Source code for the library is provided and you can modify it as you
like.

Like I said, the deal-breaker you already stated, so why would I? Again,
no offense.
 
J

joe

jacob said:
Le 29/08/10 19:32, Tim H a écrit :
Le 28/08/10 22:52, joe a écrit :

jacob navia wrote:

(1) Each container contains a special "ErrorFunction"

Too much space overhead considering that containers are the low
level (one step above primitives): 4 bytes (32-bit platform) or 8
bytes (64-bit platform). Consider that one may want to create a
hash table and use the list container for the bucket chains. It
just doesn't "feel" right have all those 8-byte pointers (64-bit
is coming on strong in the desktop world.. who buys a new computer
with a 32-bit OS anymore?) all over the place, especially since
they are not there for mainline purposes (!).

Excuse me but that is a very strange argument considering that C++
exception handling is a great consumer of space! The extremely
detailed tables that the compiler must emit for each function and
for each scope (including those functions that do NOT use exception
handling) can take up to 5-15% of the code size in many
applications [1]. THAT is a space problem that nowadays is taken
for granted. Comparing those huge tables to a single 8 byte pointer
in each container is... well, let's forget it. :)

I can instantiate thousands or millions of containers (think of that
hash of lists), but I will not have nearly that many function entry
points or active threads.

I suppose that each list has at least 2 elements, ok?

Containers need to be primitive-like IMO. Else I'd use java or something.
In C++-land though, we of course already have STL. And your point is...?

..
 
G

Goran Pusic

Really really apologize, the above is not correct.
It should be:

std::vector<T> arr=initialize();
size_t i;
try {
  for(i=0; i<arr.size(); ++i) {
    if(arr.at(i)==T()) break;
  }}

catch(const std::eek:ut_of_range& e) {
  assert(i>=arr.size());  // Time bomb! Can I really assure this
                          // assertion?

}

I have no idea what your meaning is now. In this code snippet, if
T::eek:perator== does not have some horrible bug^^^, that is completely
outside of the scope of exceptions, assert can never, ever fail.

^^^E.g. buffer overrun that often causes stack corruption in common
implementations). But if you're reading this newsgroup, you know by
now, that corrupting the stack is undefined behavior (UB). If you
invoke UB, you're lucky your hard drive isn't re-formatted :).
Somehow I don't think we're discussing UB.

So yes, if code is bug-free, you can easily assure this assertion.

You can enter the catch if e.g. T::eek:perator== throws
out_of_range^^^^^^, but "i" cannot possibly reach arr.size(), not
unless there's an even worse bug in T::eek:perator==. I am mentioning
T::eek:perator== because that's the only code we can't see. Anything else
is correct (albeit silly) code.

And code is silly because it tries to do is to find T() in arr. To do
this, you do:

std::vector<T>::const_iterator elem std::find(arr.begin(), arr.end(),
T());
if (arr.end() != elem)
// elem found

^^^^^^ if that happens, you still have a bug, because, remember,
anytime any logic_error is thrown, there is a bug. If you catch, and
not re-throw a bug, you just made another bug, because to me, hiding
bugs is a bug in itself.
I remember I read from a book that C++ does not guarantee the proper
handling if the size of thrown object is larger than the standard
exception type.

You remember wrong. In fact, this sentence is nonsense. Particularly,
mixing "size of thrown object" and size of "standard exception type"
is nonsense.
Goran.
 
G

Goran Pusic

I didn't consider unwinding when I wrote the "mapping assertions to
exceptions" thing. "Largely", I do consider "exception" to be equivalent
to abort (or wish it to be so) (abort after that which I want to precede
"abort").

That's completely wrong and is not what exceptions are for. If you
want to abort(), you abort(). abort() is orthogonal to exceptions.

Goran.
 
W

wij

I have no idea what your meaning is now. In this code snippet, if

I am very sorry for making replies too randomly. This example is
worse than the original one.
T::eek:perator== does not have some horrible bug^^^, that is completely
outside of the scope of exceptions, assert can never, ever fail.

^^^E.g. buffer overrun that often causes stack corruption in common
implementations). But if you're reading this newsgroup, you know by
now, that corrupting the stack is undefined behavior (UB). If you
invoke UB, you're lucky your hard drive isn't re-formatted :).
Somehow I don't think we're discussing UB. No

So yes, if code is bug-free, you can easily assure this assertion.
No, context is lost.
Let's say T is from a commercial company (user's code is clean).
If the company allow you to view their source, you could find there
even no 'throw' is in their codes. The company's codes are also
clean, because the responsible parts relies on other companies' codes.
At least for now to note is that the boundary between interface and
implement is weaker, since there are already many public throw types
exposed, and allowed to change silently. Finally, if the source is
found, they may consist of several instances of std::vector,... all
kind of types can be thrown without violating their specifications.

In general, no way you catch an object you know how to handle it,
even the original programmer won't know how not to rethrow in the
user's code. A function can say in what condition it may throw what
object, but the catch handler can't assert contextual condition.

Consider another similar example:

std::vector<T> v;
const size_t cap=get_needed_cap();
try {
v.reserve(cap);
}
catch(std::length_error&) {
assert(cap>v.max_size()); // Time bomb!
// T(T) may throw std::length_error. program can't have such
// assert, because context is lost.
throw;
}
You can enter the catch if e.g. T::eek:perator== throws
out_of_range^^^^^^, but "i" cannot possibly reach arr.size(), not
unless there's an even worse bug in T::eek:perator==. I am mentioning
T::eek:perator== because that's the only code we can't see. Anything else
is correct (albeit silly) code.

And code is silly because it tries to do is to find T() in arr. To do
this, you do:

std::vector<T>::const_iterator elem std::find(arr.begin(), arr.end(),
T());
if (arr.end() != elem)
  // elem found

^^^^^^ if that happens, you still have a bug, because, remember,
anytime any logic_error is thrown, there is a bug. If you catch, and
not re-throw a bug, you just made another bug, because to me, hiding
bugs is a bug in itself.

Roughly agree. I am not thinking the definition of a 'bug' yet,
instead, I am afraid of the same rethrow strategy should
also apply to others because the context is lost, catch handler
can't be sure of the program state in general in the catch handler.
Even user's private dedicated throw type is very restricted in use
to avoid being caught in or with different context.
You remember wrong. In fact, this sentence is nonsense. Particularly,
mixing "size of thrown object" and size of "standard exception type"
is nonsense.
Goran.
I mean sizeof(T), T being any of the standard exception types.
In another word, adding extra data members to std::logic_error or
std::runtime_error might not be handled properly in the stack
unwinding.
IIRC, the memory came from the book "The C++ Programming Language,
by Bjarne Stroustrup", but couldn't find it again.
It would be appropriated if anyone would verify this restriction.
 
Ö

Öö Tiib

I mean sizeof(T), T being any of the standard exception types.
In another word, adding extra data members to std::logic_error or
std::runtime_error might not be handled properly in the stack
unwinding.
IIRC, the memory came from the book "The C++ Programming Language,
by Bjarne Stroustrup", but couldn't find it again.
It would be appropriated if anyone would verify this restriction.

Most likely this is about slicing if you do not "catch
( std::logic_error const& e )" but "catch ( std::logic_error e )" when
derived type does fly. Yes ... things used in wrong way do not
work ... what a surprise.
 
N

Nick Keighley

Are you trolling?

seems a pretty reasonable question to me. I use the terms pretty inter-
changeably. You seem to using "bug" to mean "unrecoverable error" or
maybe "error in program" and "error" to mean "unexpected input" or
"environment problem". Your "errors" seem to be recoverable.
Anyway, Google it and Wikipedia it for all the details.

go, on give us a reference that agrees with you
You'll find that the first one was actually a moth and hence the term
"bug".

this is a myth. The term was in wide use before Grace's moth
 
G

Goran Pusic

Consider another similar example:

  std::vector<T> v;
  const size_t cap=get_needed_cap();
  try {
     v.reserve(cap);
  }
  catch(std::length_error&) {
    assert(cap>v.max_size());  // Time bomb!
    // T(T) may throw std::length_error. program can't have such
    // assert, because context is lost.
    throw;
  }

Ok, this is true, and I finally see your point.

But we're back to our previous discussion: you have a bug (because
length_error is logic_error, which is used to represent bugs). My
question is: when you catch any such a bug, can you fix it in your
current code? Normally not, because bugs are normally fixed by
changing the code, and that means recompilation. So a catch like that
you made is IMO useless and should just not be there. It should
propagate all the way up the stack and cause termination there. Why
termination? Because, I believe, if you hit a bug, you should not
continue running, you should fix it first.

Only thing you might want to do in your situation is to catch, add
more context, and then re-throw similar/same exception. (And I
absolutely agree with you when you say that with exceptions, context
is lost, and I also agree that this is a bad thing). For example
(warning: meta code, doesn't really compile):

....
catch(const std::length_error& e)
{
if (cap>v.max_size())
throw std::length_error("I think there's a bug in get_needed_cap.
Original error was" + e.what());
else
throw; // Something wrong with T()? Not ours, leave original error
alone.
}

As for "adding context" to exceptions, boost::exception seems very
good.
I mean sizeof(T), T being any of the standard exception types.
In another word, adding extra data members to std::logic_error or
std::runtime_error might not be handled properly in the stack
unwinding.
IIRC, the memory came from the book "The C++ Programming Language,
by Bjarne Stroustrup", but couldn't find it again.
It would be appropriated if anyone would verify this restriction.

I agree with Öö Tiib, you probably think of "object slicing", that
might happen in C++. Look it up on Wikipedia. Object slicing is not
particular to exceptions, but to object copying (and assignment).
Partial solution to this concern is to catch exception object by
reference (and better yet, a const reference), not by value. But to be
absolutely correct, you __need__ to know exact type you want to catch.

Goran.
 
N

Nick Keighley

could you leave the attributions in?


I don't think C++ was designed by hardware engineers

Well I totally agree with your above sumation, BUT what I was attempting
to say was "all " of the exception ability (even c++ ) is created (albeit
expanded) on top of the basic provided by the hardware interrupt system
and OS partnership

no. Though exceptions may look a bit like hardware interupts, they
aren't. C++ exceptions can be implemented without any support from
hardware or OS. Just because microsoft may tangle the two together
doesn't mean you have to do it that way.
of the linked list of handlers at FS:[0]. That said I was stating the
"origin" of said capability was (as I see it ) the promotion of a more
stable computer product (or least an informed exit ) as opposed to an
unlabeled crash.

putting a pretty box on the screen when your program crashes does
*not* make it "more stable".

It's not a bad feature if it apologises and sends an email to the
developers (as one application I use does)- but it doesn't increas
stability. Stability is just not crashing.
Well no I must have been ambiguous, since I did not mean that the OS would
implement an exception frame that would comply with the trylevel and scopetables
of the C compiler's SEH or the similar

you've gone all microsoft off my ass. I'm guessing trylevel,
scopetables etc. are microsoftisms. C++ is not obliged (and generally
doesn't) implement such things.
[...] But the OS itself can also have uncontrollable aspects
that it may it may have to emit (throw) an exception on it's own,

I don't think any OS uses exceptions (well kernels anyway). It just
makes 'em too hard to reason about.
OR handle an a hardware violation from the hardware interrupt
table, if the running process doesn't handle it first.

I find it odd that the currently running process would handle hardware
exceptions...

<snip>
 
G

Goran Pusic

I don't think any OS uses exceptions (well kernels anyway). It just
makes 'em too hard to reason about.

I think this just needs time. C people, that generally write OS code,
just can't wrap their heads around. When they die off, then, things
may change. ;-)

There's one, and one only, good reason not to use exceptions, and
that's performance hit.

I am willing to bet you^^^ that even when writing an OS kernel, that
hit won't actually be relevant (albeit a pure-C implementation of the
same thing __will__ still be faster, and curiously enough, will likely
be faster on "not happy" paths).

Goran.

^^^ Obviously, thinking that no one will call me on that bet;-)
 
J

Joshua Maurice

I think this just needs time. C people, that generally write OS code,
just can't wrap their heads around. When they die off, then, things
may change. ;-)

There's one, and one only, good reason not to use exceptions, and
that's performance hit.

I am willing to bet you^^^ that even when writing an OS kernel, that
hit won't actually be relevant (albeit a pure-C implementation of the
same thing __will__ still be faster, and curiously enough, will likely
be faster on "not happy" paths).

Goran.

^^^ Obviously, thinking that no one will call me on that bet;-)

Actually... arguably, if exceptions are implemented "correctly" (aka
no overhead on the normal path aka no-exception-thrown path), and the
coder codes very well, then exception use can actually increase the
execution speed of the normal path code over error return code using
code. Branching on error return codes takes time, at the very least a
slot in the instruction pipeline, and at worst a stalled pipeline from
an incorrect branch prediction.

Exceptions do take more runtime storage to implement, which generally
isn't a problem on a system with virtual memory. However, exception
use also tremendously pessimisess the worst case, which may not be
appropriate for a robust kernel.
 
J

joe

Goran said:
That's completely wrong and is not what exceptions are for. If you
want to abort(), you abort(). abort() is orthogonal to exceptions.

Note the quotes. I was using the term in a more general sense (as in,
"exception" vs. "error") and not meaning C++ exception machinery.
 
J

joe

Nick said:
seems a pretty reasonable question to me. I use the terms pretty
inter- changeably.

I don't see how you could. If you consider "bug" and "error" to be the
same thing, then what do you call those things that return codes and C++
exceptions are used for?
You seem to using "bug" to mean "unrecoverable
error" or maybe "error in program" and "error" to mean "unexpected
input" or "environment problem".

Well, I avoid using the word 'error' when describing the word 'bug'
because in my vocabulary, they are not the same thing.
Your "errors" seem to be recoverable.

Not necessarily. Manageable or handleable.
go, on give us a reference that agrees with you

That a "bug" and an "error" are different animals?
this is a myth. The term was in wide use before Grace's moth

So fill us in on the origin of that trivia then if you know. Don't just
leave us hanging!
 
J

joe

Daniel said:
To borrow from Bertrand Meyer, we have three notions. The function
succeeds, the function fails do to a precondition violation, and the
function fails do to a postcondition violation. This discussion
pertains to the latter two.

A function can easily detect a precondition violation, but it can't
know what to do in order to fix the problem, that's the job of some
higher level. So for this kind of problem, an exception (or return
code if exceptions aren't used) is the correct option.

A function can't detect a postcondition violation; if the
postcondition is violated (when the precondition was met,) then the
programmer did something wrong. An exception shouldn't be thrown in
this case, the programmer should fix the code instead.

I know "precondition violation" and "postcondition violation" are not
as sexy as "bug" and "error," but that's how I roll I guess. :)

Yes, that's all good stuff... but, ... um, what is your point? I mean,
you throw in all those terms and then don't relate them to the (small)
issue at hand: "bugs" vs. "errors". Probably because bringing in the
DETAIL terms like "pre/postcondition" doesn't help things in that regard.

So, let's keep our eyes on the ball and have fun trying to keep a single
thread in these newsgroups ON TOPIC (i.e., the thread topic). Funny I
should say that, since certainly this "bugs vs. errors" thing is a
subtopic (!?).
 
K

Kai-Uwe Bux

Daniel said:
To borrow from Bertrand Meyer, we have three notions. The function
succeeds, the function fails do to a precondition violation, and the
function fails do to a postcondition violation. This discussion pertains
to the latter two.

A function can easily detect a precondition violation,

Not always: e.g., many algorithms that take a pair of iterators have the
precondition that those iterators denote a valid range. However, unless
there is some special support from the iterators, any attempt to verify that
results in UB if the precondition is violated.
but it can't know
what to do in order to fix the problem, that's the job of some higher
level. So for this kind of problem, an exception (or return code if
exceptions aren't used) is the correct option.

A function can't detect a postcondition violation; if the postcondition
is violated (when the precondition was met,) then the programmer did
something wrong. An exception shouldn't be thrown in this case, the
programmer should fix the code instead.

But the same can be said about (at least some) precondition violations. It's
just a different programmer who has to fix his code: namely the guy who is
calling a function without observing its contract.
I know "precondition violation" and "postcondition violation" are not as
sexy as "bug" and "error," but that's how I roll I guess. :)

More to the point: precondition and postcondition violations are concepts
that appear somewhat orthogonal to the distinction of bug vs error.


Best

Kai-Uwe Bux
 
J

joe

Kai-Uwe Bux said:
Not always: e.g., many algorithms that take a pair of iterators have
the precondition that those iterators denote a valid range. However,
unless there is some special support from the iterators, any attempt
to verify that results in UB if the precondition is violated.

There is the concept of CHECKED and UNCHECKED violations, of course.
But the same can be said about (at least some) precondition
violations. It's just a different programmer who has to fix his code:
namely the guy who is calling a function without observing its
contract.


More to the point: precondition and postcondition violations are
concepts that appear somewhat orthogonal to the distinction of bug vs
error.

Certainly I need to learn to be tactfully succinct! Good job.
 
J

joe

Daniel said:
I guess I did a lousy job of tying my comments to the topic. The
question was "what do you call those things that return codes and C++
exceptions are used for?" My answer is, "precondition violations."

Oh? Just that? What about postconditions and invariants? I was alluding
that there may be more (at least I'm not ready to place a seal on just
those things as constituting all errors). Perhaps if one just uses the
terms conceptually/generally. A definition of error that is "failure of
or failure to achieve preconditions, postconditions, invariants" seems
much less broad (and therefor less correct) than "anything that will
cause a function to fail". Of course, "bugs" are not included in the
latter.
 
J

joe

joe said:
Oh? Just that? What about postconditions and invariants? I was
alluding that there may be more (at least I'm not ready to place a
seal on just those things as constituting all errors). Perhaps if one
just uses the terms conceptually/generally. A definition of error
that is "failure of or failure to achieve preconditions,
postconditions, invariants" seems much less broad (and therefor less
correct) than "anything that will cause a function to fail". Of
course, "bugs" are not included in the latter.

Nor the former.
 
J

joe

Daniel said:
When a library writer finds that a postcondition or invariant failure
has occurred in his code, it should be fixed. When a precondition
failure occurs in his code, he should throw an exception, there is no
way for him to "fix" it.

I disagree. Assertions should be used for the above.
From what I see, the bigest question new programmers have about the
exception mechanism is about when to use it. They are told vague
generalities but not given anything concrete (they are told to use
them for "errors" and "exceptional situations".) I'm trying to clear
out some of the vagueness.

Well you are failing miserably then for you just above suggested that
exceptions were useful for bug control.
As Stroustrup put it:

... the author of a library can detect run-time errors but does not
in general have any idea what to do about them. The user of a
library may know how to cope with such errors but cannot detect
them ­ or else they would have been handled in the user¹s code and
not left for the library to find. The notion of an exception is
provided to help deal with such problems.

What Stroustrup is describing above are precondition violations.



Exceptions should not be (and in fact, cannot be) used for "all
errors," unless you define "error" as a precondition violation.

What mechanism one uses is strictly personal preference. Of course
though, one may be "trapped" into using C++ exceptions when usage of the
constructs of the language dictate so.
To me, your two quotes above are equivalent, so neither is less
correct or less broad than the other.

Well if you can't grasp the very different level of abstraction, I'll
leave you to your own devices to figure it out. Consider adopting my
"definition" of "error": Anything that can cause a function to fail, but
is not a bug.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,754
Messages
2,569,528
Members
45,000
Latest member
MurrayKeync

Latest Threads

Top