Advertisement

Exceptions - Various ramblings

Started by February 21, 2001 06:11 AM
17 comments, last by rowbot 23 years, 11 months ago
Windows EH uses SEH handling. I believe it access the FS register and saves the function address onto the stack. (for stack unwinding), so the setup still has some clock cycles.

What I meant was by using C++ EH, your win32 program doesn''t prevent a SE ( structured exception ) from happening.

quote:
Original post by Anon Mike
Last time I looked, VC used the same stack...

You can handle stack overflows because you get the exception before you''re actually out of stack. The exception happens when the final page of the stack is touched, not when you''re out completely.



That is bad because what happens if the exception object I throw contains local variables too? Where would those local variables get initialized on the stack? That is the stack I''m talking about.

I read somewhere that the implementation is thrown objects uses a different stack. I didn''t really bother to verify it since it makes sense to me.
quote:
Original post by Kensai
1) exception handling is for exceptional situations, not flow control. think deeply on this


quote:
1) exception handling is for exceptional situations, not flow control. think deeply on this


quote:
1) exception handling is for exceptional situations, not flow control. think deeply on this


I copied it three times because it's worth repeating three times.

You should see no decrease in performance if your code contains exceptions that are not being called. People who say they see a decrease in performance are not following Kansai's rule #1.

You WILL see larger source code. This is because whenever you put a "throw" in your code, stack objects must be unwound. However, this code is only executed in exceptional situations , and should not execute in the normal flow of your program.

We wrote a script parsing program here that controls simulation hardware. The script language had about 50 built-in functions, with optional parameters to most, etc., etc. About 10 times in each function you saw this code:
if (num_args < REQUIRED_NUM_ARGS){  sprintf (s, "Error in OPEN_HARDWARE: requires %d arguments",    REQUIRED_NUM_ARGS);  log->write (s, LOG_LEVEL_CRITICAL);  m_error = true;  return false; // caller checks return value}  


This same block of code was copied around about 300 times. Did the user get uniform error reporting? No. Sometimes the function name was included, sometimes not. Every time a function was called, it used its return value to indicate an error (which could have been used for much more useful things).

When I took over the project, one of the things I did was replace all of these statements to throw exceptions. You just have the sprintf line, and then a throw runtime_exception (s). Then, in the main loop that parses and calls each script line, I caught the exception. This allowed my code to work like this:

try{  script->*pFunc (args); // call script function}catch (exception &e){  sprintf (s, "Error in file %s, line %d, function %s: %s",     script->cur_file, script->cur_line, script->cur_func,    e.what ());  log->write (s, LOG_LEVEL_CRITICAL);  error = 1;  return;}catch (...){  sprintf (s, "Unknown error in file %s, line %d, function %s",    script->cur_file, script->cur_line, script->cur_func);  log->write (s, LOG_LEVEL_CRITICAL);  error = 1;  return;} 


To my users, all the errors have the same format with the script file, line and function name followed by reason for error. Not only that, but now my functions look much cleaner:

if (num_args < REQUIRED_NUM_ARGS){  sprintf (s, "requires %d arguments", REQUIRED_NUM_ARGS);  throw invalid_argument (s);} 


And if your functions call other functions, there's no need to check return codes and return all the way out on error; all of that is done automatically by C++ EH.

Proper use of exceptions makes your code more readable, more maintainable, more robust and does not cause any decrease in performance. The action of throwing an exception is slow in itself, but exceptions should not be thrown unless the program would otherwise crash--and who cares how fast you crash?

edit--sorry, I always put a 'code' at the end rather than a '/code' accidentally.

Edited by - Stoffel on February 24, 2001 1:54:49 PM
Advertisement
Exception handling can be used for flow control.

Take allocating virtual memory as an example. You can reserve memory, but allocate it only as needed. That''s flow control. And it''s only possible using SEH, because using the translator function you''re actually complicating things too much, or so it seems.

C++ EH on MSVC is implemented using SEH, so it is always bulkier than pure SEH (unless, of course, the programmer chooses to make his SEH code bulky). It''s simply on a higher level than SEH.

Microsoft uses SEH for things like termination of traversal. When you''re going through a list, and you hit a NULL pointer, the function jumps into the exception handler, and you don''t have to write "weird" logic to check (if (!p) ). Also, suppose malloc returns a NULL. Instead of writing if (!pMem) {error handler}, you can count on your exception handler to execute. But that is beside the point (since that falls into the category of error handling).
VK
SEH could give you a bit of an advantage. The unusually good thing about Windows'' SEH is that it allows one to fit his thoughts to system-level, rather than application-level, design.

For instance, in domains with pointers SEH is invaluable. It provides alternative solutions to old problems by catching hardware exceptions, handling gracefully, and even allowing the program to continue if the problem can be solved. In your case, the program can continue provided the user corrects the arguments mistake. This is doable:
// EXCEPTION FILTER
Filter (DWORD dwException, DWORD* pdwArguments, UINT uiNumArgs ) {
if (dwException == EXCEPTION_BAD_ARG_COUNT) {
// Open a dialog box to fix the error.
...
// Try to continue again.
return EXCEPTION_CONTINUE_EXECUTION;
}
..
}

// SCRIPT FUNCTION
// Script code
ReTryArguments: ...
if (num_args < REQUIRED_ARG_COUNT) {
RaiseException (EXCEPTION_BAD_ARG_COUNT,
(DWORD)&CountedArgArray);
goto ReTryArguments; // The powerful albeit "dangerous" goto is necessary, because this is where execution continues after the exception is continued

In the code above, the program attempts to fix the problem. It can be fixed no matter where it occured. C++ EH lacks this funcitonality. This is understandable, since it is system- and even architecture-specific.

In domains with data processing (applications), C++ EH is preferred. This is in part due to its architecture. It allows a programmer to throw and catch objects of any type, thus swiping away the filter function of SEH. However, you don''t always want it swiped away, if only because SEH allows you a lot more control over what happens to the exception.

My, as they like to write it, $0.02.
VK
quote:

who cares how fast you crash



EXACTLY!
I get so pissed at those people claiming C++ exceptions suck ''because they are so much slower than a return''
Its just comparing apples and oranges, ''return'' serves a totally different purpose than ''throw''.





ALL YOUR BASE ARE BELONG TO US

INTEL INSIDE: The worlds most widely used warning label.
-------homepage - email
There is no doubt exception handling is a nice tool, but with most (but not all) existing C++ compilers, enabling the "exception handling" option causes all functions to have a bit more overhead (whether you hit an exception or not).

This is what some people have issue with (and violates C++''s guiding principle of "you pay only for what you use").

Whether the performance hit is significant or not depends on what you are doing, what compiler you are using and what you consider significant.

I keep it disabled on my game projects. In a game, often the things you would throw an exception for are considered a fatal error, so "handling" it doesn''t buy you much (depending on the platform etc.).

Usually the technique of "I won''t worry about this, and if something bad happens I will clean it up in an exception handler" is suboptimal vs. actually keeping things under control in the first place (although its no doubt more difficult).

Still, exception handling has its place (and computers are so fast nowadays...)
Advertisement
quote:

There is no doubt exception handling is a nice tool, but with most (but not all) existing C++ compilers, enabling the "exception handling" option causes all functions to have a bit more overhead (whether you hit an exception or not).



There is only one compiler I know of that doesn''t implement correctly, and sadly that is also the most used compiler, namely the compiler of Visual C++ 6.0. You can''t blame a great technique for its poor implementation in one compiler. I really hope they fix this for VC7.

quote:

Usually the technique of "I won''t worry about this, and if something bad happens I will clean it up in an exception handler" is suboptimal vs. actually keeping things under control in the first place (although its no doubt more difficult).



Thats where you are completely wrong. Who defines wheter an exception is fatal? In every case, NOT the API/engine developer. You should always allow the application to deal with errors. C++ exceptions allow this very elegantly through stackunwinding etc.





ALL YOUR BASE ARE BELONG TO US

INTEL INSIDE: The worlds most widely used warning label.
-------homepage - email
What do you mean, "fix it"?

It''s actually appropriate to implement it using SEH. And the part about overhead per function is rubbish. SEH requires, as far as I know, only one statement to set up a frame: mov fs, [X].
VK
quote:

Microsoft uses SEH for things like termination of traversal. When you''re going through a list, and you hit a NULL pointer, the function jumps into the exception handler, and you don''t have to write "weird" logic to check (if (!p) ).



I don''t think so. I''ve debugged a lot of code and done a lot of developement and I would have noticed if MS did this with any frequency because it really would have made life a bitch figuring out whether or not I can safely ignore all those first chance exceptions. Can you point at an example?

Plus, this is *exactly* the wrong way to use exception handling, SEH or not. Handling the resulting AV in an exception handler is going to be orders of magnitude slower than just checking for NULL. A NULL terminated linked list is *not* an exceptional or unexpected condition. If you try to treat it as one you''re going to be blowing any chance of good performance out the window and make life real hell for whoever needs to debug your code.

-Mike


-Mike

This topic is closed to new replies.

Advertisement