Portable Use And Disabling Of Exceptions

Started by
30 comments, last by Bacterius 11 years, 1 month ago

I really want to use exceptions in my code, because they are about everything that I've ever wanted in terms of handling exceptional occurrences. However, I was concerned about the overhead of exceptions, in either speed or space. However, I was reading and reading about exceptions, and I'm starting to accept that it is worthwhile to use them, and there aren't as many platforms that I support that don't have compilers that allow standard exceptions as there once was.

So, I'm looking for advice on it. For these platforms, can you tell me if standard exceptions are supported by any compilers and allow them to be disabled, and bear in mind, I don't support all of them:

Xbox
Xbox 360
PS2

PS3

PSP

Pandora
Dreamcast

Nintendo DS

Symbian OS, any versions after s60v3 (I hear the compilers finally support standard exceptions)
GCC on Windows and Unix derivatives
Visual Studio on Windows
Android (I hear the NDK does now)

iOS (mixing Objective-C with C++)

Additionally, for most platforms that support disabling exceptions through a compiler switch, what happens when a throw is encountered? Does it raise some sort of signal?

EDIT:
My most frequent use-case for exceptions is to indicate something that should not happen in a normal work load has happened (out of memory, data that goes into an internal structure from external code is invalid, attempting to use something that is in a valid but explicitly unusable state). In these cases, I would use a debugger to figure it out, but swallowing exceptions is against my style, so they would only occur in areas that must be fixed; a perfect run should never throw them, but still have the option of handling them just in case, like the extremely rare circumstance of expecting memory allocation to fail if you allocate based on input, and displaying an error asking for a different value, though nothrow new could just as easily be used.

For the sake of conversation, I use assertions for things that should never happen, and I use status codes for states that are expected to occur. It is very likely that trying to open a file will result in failing to find it, so returning a code indicating this is ideal. Ensuring that the pointer to my main structure isn't null should be left up to an assert; if it fails, then it is a logic error, that shouldn't have anything to do with what is passed from external code.

Further clarification, for how I use each:

1. Exceptions are for public interfaces that interact with external code. An exception signifies that something unexpected has happened, and it is the external code's fault.

2. Returning status codes is for both internal and external code. Status codes indicate that the expected behavior is for one of many events to occur, not all of them positive, though they are _recoverable_.
3. Using assertions is for internal code. The external code should not be able to cause an assertion.

If anyone has tips on how to improve this methodology, don't hesitate to contribute.

Advertisement

So, I'm looking for advice on it. For these platforms, can you tell me if standard exceptions are supported by any compilers and allow them to be disabled, and bear in mind, I don't support all of them:
Xbox
Xbox 360
PS2
PS3
PSP
Pandora
Dreamcast
Nintendo DS
Symbian OS, any versions after s60v3 (I hear the compilers finally support standard exceptions)

Don't know about these...

GCC on Windows and Unix derivatives

Yes.

Visual Studio on Windows

Yes.

Android (I hear the NDK does now)

Yes.

iOS (mixing Objective-C with C++)

Yes.

Additionally, for most platforms that support disabling exceptions through a compiler switch, what happens when a throw is encountered? Does it raise some sort of signal?

Depends on the compiler. For G++, it calls abort().

[size=2][ I was ninja'd 71 times before I stopped counting a long time ago ] [ f.k.a. MikeTacular ] [ My Blog ] [ SWFer: Gaplessly looped MP3s in your Flash games ]

Calling abort() is ideal behavior in the absence of exceptions, in my opinion, and I'm relieved to hear that. My three main reasons for avoiding exceptions so far are (1) lack of compiler support on some fringe cases of my supported platforms, (2) speed penalty, especially if the compiler won't let you disable them, and (3) what happens if the compiler does let you disable them?

Also, see my edit, if your post came while I was revising.

GCC on Windows and Unix derivatives
Visual Studio on Windows
Android (I hear the NDK does now)
iOS (mixing Objective-C with C++)

All of the above fully support exceptions.

PS3

As I understand it, the PS3's hardware isn't well suited to exceptions (i.e. the SPU programming model). Then again, it's also not very well suited to software that wasn't written with the SPU in mind - the chances that your general-purpose library will be used here are quite slim.

Xbox 360

Supported, but disabled by default.

Xbox
PS2
Dreamcast
Symbian OS

Are any of these actually realistic target platforms for a modern game development library? Might as well throw BeOS and Amiga into the mix...

Tristam MacDonald. Ex-BigTech Software Engineer. Future farmer. [https://trist.am]

My most frequent use-case for exceptions is to indicate something that should not happen in a normal work load has happened (out of memory, data that goes into an internal structure from external code is invalid, attempting to use something that is in a valid but explicitly unusable state). In these cases, I would use a debugger to figure it out, but swallowing exceptions is against my style, so they would only occur in areas that must be fixed; a perfect run should never throw them, but still have the option of handling them just in case, like the extremely rare circumstance of expecting memory allocation to fail if you allocate based on input, and displaying an error asking for a different value, though nothrow new could just as easily be used.

May I ask why you can't just use assert()?

[size=2][ I was ninja'd 71 times before I stopped counting a long time ago ] [ f.k.a. MikeTacular ] [ My Blog ] [ SWFer: Gaplessly looped MP3s in your Flash games ]

the chances that your general-purpose library will be used here are quite slim.


I figured, after reading a lot about the architecture. Mostly out of curiosity; if I ever do write homebrew for the console, I'd be doing quite a bit of rewriting with the architecture in mind, or using a 3rd party library, so knowing if exceptions are usable would still be good knowledge; ideally, I'd be hitting the exceptions while testing on a machine that is set up to perform like it. The way I outline using exceptions would make it so that hardware differences would result in triggering an assertion; anything that would fail the same way on a test machine as it would on the target platform would be covered by exceptions.

May I ask why you can't just use assert()?

assert() can't be handled, so I would reserve it for the most extreme cases when an invariant is violated, and the execution _cannot_ proceed. For an example, a UTf-8 string class.

I'd return an error code if a character isn't found in the string. This is one of many expected results.
I'd throw if someone inputs invalid data. This is unexpected, and someone else's fault. They can catch it an fix it if they feel compelled to do so. I'd just validate/sanitize first.
I'd assert if I encounter invalid data in the private string processing functions. This should never happen, and there's no way to recover. If the string is now invalid when it shouldn't be, I have no idea what the string should be, or what to do with it. The behavior cannot match what it would be if the string were perfect, and a program that centers entirely around string handling will be devastated by this event.

Ok, so in general, there are 3 sources of error:

  1. Bad input (wrong input, wrong charset, etc).
  2. Negative outcomes of normal conditions (divide by zero, null pointer, etc).
  3. Bad Shit™ (out of memory, solar flares, etc.)

You can (in theory) always sanitize/discard (1), and always anticipate (2). Unfortunately, you just have to deal with (3) as it occurs.

***

C++ is notoriously bad about (2), so you are actually forced to anticipate those (and mostly, either prevent or ignore them).

You should always be validating (1) at the surfaces of your system, so those can be handled with a simple error code return.

But I've never found a decent way to deal with (3) in the absence of exceptions...

Tristam MacDonald. Ex-BigTech Software Engineer. Future farmer. [https://trist.am]


Ok, so in general, there are 3 sources of error:

  1. Bad input (wrong input, wrong charset, etc).
  2. Negative outcomes of normal conditions (divide by zero, null pointer, etc).
  3. Bad Shit™ (out of memory, solar flares, etc.)

You
can (in theory) always sanitize/discard (1), and always anticipate (2).
Unfortunately, you just have to deal with (3) as it occurs.

***

C++ is notoriously bad about (2), so you are actually forced to anticipate those (and mostly, either prevent or ignore them).

You should always be validating (1) at the surfaces of your system, so those can be handled with a simple error code return.

But I've never found a decent way to deal with (3) in the absence of exceptions...


I agree, for the most part, however (2) is a bit of a gray area.

There are many instances where I'd return an error code, throw an exception, or trigger an assertion for these things.
Some examples:

Return an status code:
Specifying an aspect ratio with a zero denominator. It'd be business as usual to report failure in some cases, or ask the user for another input.
A function that manipulates a structure, or if the structure is null, allocates a new one instead. The return value would change from being the one passed to the newly allocated buffer.
strtok() is a murky example; passing null is valid, and will result in different behavior than passing a non-null value. However, if you pass null without first making a call with a valid pointer, there is an error. Thus, passing null may or may not be an error condition. And if it isn't an error condition, the return value can still change. You might choose to have an implementation throw on receiving null on the first call, or simply return null, as if the string were empty.

Throw an exception (not exactly null pointer or divide by zero):
Failure to allocate memory. This is capable of terminating an application if not handled, but it can be valid if you allocate a large buffer to do things faster, but if it fails, choose to slowly handle one element at a time.
Reading in an XML file. The file exists, the header is fine, but it encounters a control character in the text. It's possible to provide a lot more information about what happened if you throw an exception (what it was and where), rather than return a simple status code saying "SYNTAX_ERROR" or "INVALID_CHARACTER".
Access a vector element out of bounds. There is no way that you can return a unique error value, and an assertion should be avoided (in my opinion) because it was caused by code outside of the vector's implementation. It may be possible to recover from this, so the caller should be given the opportunity to do so.
A divide by zero is caused by code no-where near where the division occurred, so a return code can't help it. If it doesn't violate an invariant of the class, it may be a good idea to allow the caller to handle it, like a scientific calculator that is allowed to have either operand be zero, so dividing two variables wouldn't be anything noticeable until the values are evaluated. Then assert or throw.

Trigger an assertion:
An image's bytes per pixel is zero, when there's an invariant in place that it must be valid. This shouldn't happen.
A memory allocator's pool is null. That can't be good, and chances are, this can't be recovered due to being unable to allocate.
A HUD drawing function tries to render the text in a null string. This should have been handled somewhere way higher up.

Some of these, are merely differences of opinion. For instance, I'd make out of memory an exception, and solar flares an assertion.

I agree, for the most part, however (2) is a bit of a gray area.

Everything in (2) is things that you can't catch in C++.

There is no portable way to recover from dereferencing a null pointer, or a divide by zero exception (and on a PowerPC chip, 1/0 = 0, so you won't even know it has occurred).

Tristam MacDonald. Ex-BigTech Software Engineer. Future farmer. [https://trist.am]

dereferencing a null pointer


Ah, _dereferencing_ a null pointer. Yeah, that gives a better idea of what you mean. Yeah, for those events, I can only think of avoiding them.

However, as far as (3) goes, I draw a division of what to do based on whose fault it was. If it was their fault, the problem can possibly be remedied, and thus a catchable exception should be thrown. If it was my fault, due to invariants being violated, or things that shouldn't be possible occurring, I use an assertion.

Though, we may be agreeing perfectly on this, because solar flares aren't something the language is capable of detecting. :)

This topic is closed to new replies.

Advertisement