Dynamic Memory and throwing Exceptions

Started by
18 comments, last by Hodgman 9 years, 1 month ago

Here's a good game developer's view on the matter:
http://bitsquid.blogspot.com.au/2012/01/sensible-error-handling-part-1.html

Personally, even though they're not really a performance concern any more, I still choose to avoid them in C++ because writing exception-safe C++ code is hard. As in, still hard for someone who's been practicing C++ for a decade. I use them in C#/Java/Python/etc though...
Also, designing code in such a way that errors aren't a thing you have to deal with is the first priority.


I agree with a lot with what this blog post has to say, but at the same time the one part where they go over:

Exceptions
Rather than crashing isn’t it better to throw an exception? If the exception isn’t caught we get a crash, just as before. But we also have the option, if we really want to, to catch the exception and handle the error. It would seem that by using exceptions we can have our cake and eat it too.

I do and I don't agree with this part.
Let's say I have the following:


TypeObject CreateType(int value)
{
     TypeObject a;
     switch(value)
     {
         case 1:
         a.type = 1;
         break;
 
         case 2:
         a.type = 2;
         break;
 
         case 3:
         a.type = 3;
         break;
 
         default:
         throw std::runtime_error("Failed to create object. Unknown type: " + value);
         break;
     }
 
     return a;
} 

The first thought that I have is "If I don't know what the type is force the program to crash". Then have:


int main()
{   try
    {
       TypeObject obj = CreateType(4);
    }
    catch(exception &e)
    {
       //Logging of e.what() to file
    }
}

To catch the exception for logging. I mean is this a bad idea?

What happens if I do the above catch and I run into a access violation? Will it still get caught since its just a base exception I'm looking for?

Maybe worth recalling somebody isn't AAA. Exceptions are extremely handy and considering the first few posts of this thread are clearly written by someone who doesn't have an accurate view of what's going on, I'd suggest to stick to what C++ suggests to do as canon as long as there isn't a specific product to talk about.

I mean this guy is right. When it comes to exceptions I'm not sure what I should be doing.

I mean the idea behind "crashing" is bad to me, only because I have trouble figuring out why and where (I'm talking about outside the debugger here) the crash happened

Advertisement

Here's a good game developer's view on the matter:
http://bitsquid.blogspot.com.au/2012/01/sensible-error-handling-part-1.html

Personally, even though they're not really a performance concern any more, I still choose to avoid them in C++ because writing exception-safe C++ code is hard. As in, still hard for someone who's been practicing C++ for a decade. I use them in C#/Java/Python/etc though...
Also, designing code in such a way that errors aren't a thing you have to deal with is the first priority.


I agree with a lot with what this blog post has to say, but at the same time the one part where they go over:

Exceptions
Rather than crashing isn’t it better to throw an exception? If the exception isn’t caught we get a crash, just as before. But we also have the option, if we really want to, to catch the exception and handle the error. It would seem that by using exceptions we can have our cake and eat it too.

I do and I don't agree with this part.
Let's say I have the following:



TypeObject CreateType(int value)
{
     TypeObject a;
     switch(value)
     {
         case 1:
         a.type = 1;
         break;
 
         case 2:
         a.type = 2;
         break;
 
         case 3:
         a.type = 3;
         break;
 
         default:
         throw std::runtime_error("Failed to create object. Unknown type: " + value);
         break;
     }
 
     return a;
} 
The first thought that I have is "If I don't know what the type is force the program to crash". Then have:


int main()
{   try
    {
       TypeObject obj = CreateType(4);
    }
    catch(exception &e)
    {
       //Logging of e.what() to file
    }
}
To catch the exception for logging. I mean is this a bad idea?
What happens if I do the above catch and I run into a access violation? Will it still get caught since its just a base exception I'm looking for?


Maybe worth recalling somebody isn't AAA. Exceptions are extremely handy and considering the first few posts of this thread are clearly written by someone who doesn't have an accurate view of what's going on, I'd suggest to stick to what C++ suggests to do as canon as long as there isn't a specific product to talk about.

I mean this guy is right. When it comes to exceptions I'm not sure what I should be doing.
I mean the idea behind "crashing" is bad to me, only because I have trouble figuring out why and where (I'm talking about outside the debugger here) the crash happened


Two misconceptions here.

First - exceptions are not "force code to crash". Exceptions are "something happened I can't handle, but my caller might, so tell them". Your example of an invalid type is a good one - you can't decide what you want to do if an invalid type is passed, so you pass the responsibility to the caller. Part of this is making sure you pass enough information to the caller so they know what happened - and not just throwing a generic "runtime error".

Second - the caller should only handle exceptions they can actually do anything about, and let the rest fall through. If you do not expect an exception, don't catch it. Don't catch exceptions you can't handle, and don't write (...) blocks. The exception, of course, is when you're writing code that needs to know an exception happened to clean up resources, like if you were writing std::vector yourself. Though even then you trap the exception, clean up, and then re-throw so the caller can handle it.

Crashing is not a bad thing for the developer. Crashes/unhandled exceptions can be caught by the debugger at the throw site so they can figure out what's wrong. Crashing for the end user is usually not pretty - but it's generally better then corrupting memory and pretending nothing bad happened. Most professional software these days has a top-level crash handler that will catch exceptions and crashes, perform a memory dump, and allow the user to submit a bug report. (You don't need exceptions to do that though, in Windows, a Structured Exception Handler will be enough)

Don't catch exceptions you can't handle, and don't write (...) blocks


What is a (...) block?

Most professional software these days has a top-level crash handler that will catch exceptions and crashes, perform a memory dump, and allow the user to submit a bug report. (You don't need exceptions to do that though, in Windows, a Structured Exception Handler will be enough)


From what I understand, isn't this just like a Structured Exception Handler?


int main()
{   try
    {
       /*All of my other code*/

       TypeObject obj = CreateType(4);

       /* Some other code in my main funciton */
    }
    catch(exception &e)
    {
       //Logging of e.what() to file
    }
}

Don't catch exceptions you can't handle, and don't write (...) blocks


What is a (...) block?


Sorry - it's where the exception specification is an ellipsis and doesn't specify the type. It will catch everything that is thrown, not just things that inherit from some exception class.


try
{
  throw 1; // this won't be caught
}
catch(std::exception& ex)
{
  std::cout << "Caught something!";
}

try
{
  throw 1; // this now will be caught
}
catch(...)
{
  std::cout << "Caught something!";
}
It's usually frowned upon because there is rarely anything you can do to recover from such a catch block, because you don't know what kind of exception is thrown. The - heh - exception being something that is managing resources and so doesn't care what is thrown, but needs to clean up if anything is thrown.

It's also useful if you're calling a function that can throw in an destructor to prevent the exception from escaping the destructor (which is a big no-no in C++)

Most professional software these days has a top-level crash handler that will catch exceptions and crashes, perform a memory dump, and allow the user to submit a bug report. (You don't need exceptions to do that though, in Windows, a Structured Exception Handler will be enough)


From what I understand, isn't this just like a Structured Exception Handler?



int main()
{   try
    {
       /*All of my other code*/

       TypeObject obj = CreateType(4);

       /* Some other code in my main funciton */
    }
    catch(exception &e)
    {
       //Logging of e.what() to file
    }
}

No, that only catches C++ exceptions - and only things that inherits from the "exception" class at that.

If you dereferenced a null pointer, it wouldn't be caught, because that doesn't fire a C++ exception.

Structured Exception Handling (SEH) involves using "__try" and "__except" (at least in Microsoft's compiler) and doesn't even require C++. You can also set up an unhandled exception filter which a lot of software uses to make dumps to be reported to the developer, as mentioned above.

Note that structured exceptions do NOT clean up the stack like C++ exceptions do (it doesn't have the information to do so, after all). In general they are harder to use, so I would recommend sticking with C++ exceptions and only using SEH for dump creation/error reporting.


I mean the idea behind "crashing" is bad to me, only because I have trouble figuring out why and where (I'm talking about outside the debugger here) the crash happened

That's easily resolved.

It takes only a few minutes to configure your application to write a mini dump when it crashes.

Then you need to get into a habit of preserving all the source code and intermediate build files for whatever you release. (The first is likely in version control, the second can be kept in a build server.)

With all of that combined you can take a crash report and open all the source in the debugger, frozen at the instant of the crash. Usually (but not always) that is all you need to hunt down the problem.

Structured Exception Handling (SEH) involves using "__try" and "__except" (at least in Microsoft's compiler) and doesn't even require C++. You can also set up an unhandled exception filter which a lot of software uses to make dumps to be reported to the developer, as mentioned above.

Note that structured exceptions do NOT clean up the stack like C++ exceptions do (it doesn't have the information to do so, after all). In general they are harder to use, so I would recommend sticking with C++ exceptions and only using SEH for dump creation/error reporting.


Is SEH not meant to be used by code that requires or has objects? (Kind of seems ridiculous if that is the case)
I get an error saying "Cannot use __try in functions that require object unwinding"

That's easily resolved.

It takes only a few minutes to configure your application to write a mini dump when it crashes.

Then you need to get into a habit of preserving all the source code and intermediate build files for whatever you release. (The first is likely in version control, the second can be kept in a build server.)

With all of that combined you can take a crash report and open all the source in the debugger, frozen at the instant of the crash. Usually (but not always) that is all you need to hunt down the problem.


This may seem stupid, but are there alternatives to writing a minidump through window's api?


This may seem stupid, but are there alternatives to writing a minidump through window's api?

Google Breakpad is a good alternative, especially if you target non-Microsoft platforms. Mozilla uses it pretty extensively.

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

std::unique_ptr and std::shared_ptr are both 'smart pointers', just to be clear. They serve similar purposes, but are used in different circumstances.

This may seem stupid, but are there alternatives to writing a minidump through window's api?


Write better logging output, possibly use custom asserts.

Currently my stack-traces aren't working (I'm using a third-party library, and I think I configured it wrong - MinGW doesn't make this easy), but I output as much 'automated' details as I can, and also provide more contextual details when asserting. Currently I output to .html files with some embedded CSS. It's all really sloppy and duck-taped together, but provides a good first-approximation at figuring out what problem occurred.

Here's some example output:
[spoiler]4e7e13bb82.png[/spoiler]
All the data except the last three lines are auto-generated using information provided either by the compiler, or by myself in other parts of the code.

Once the work is done behind the scenes and wrapped up neatly (or less-than-neatly, in my case), the higher-level code to output that is as simple as:


Log::Message(MSG_SOURCE("ConfigFile", Log::Severity::Error))
    << "Previously failed to load a config file, and it was auto-generated. The config file hasn't been updated since the auto-generation.\n"
    << "Confile file: '" << Log_HighlightCyan(GetFilenameFromPath(filepath)) << "'\n"
    << "Path: " << Log_DisplayPath(filepath) << Log::FlushStream;


That one just outputs a message, it doesn't crash (because it's not a critical error). If I want to crash, I do this:


Assert(SerializeFromFile(this->Details.TextureDetails, filepath), "Failed to load the 'TextureDetails' resource file.");

Which checks that the statement is true (in this case, that the function returned true returned true), or else it outputs the message using the same logging system, and then crashes with a pop-up box being displayed to the user.

In a previous post, I've given some examples of what data you might want to output (you'll have to scroll down to the bottom of the post, as most of it is unrelated).
ApochPIQ describes the benefits of embedding a tiny web server in your code. Others have done this as well.
Others talk about how to provide more context for your errors (I'm using this in my screenshot above, among other things).

Structured Exception Handling (SEH) involves using "__try" and "__except" (at least in Microsoft's compiler) and doesn't even require C++. You can also set up an unhandled exception filter which a lot of software uses to make dumps to be reported to the developer, as mentioned above.

Note that structured exceptions do NOT clean up the stack like C++ exceptions do (it doesn't have the information to do so, after all). In general they are harder to use, so I would recommend sticking with C++ exceptions and only using SEH for dump creation/error reporting.


Is SEH not meant to be used by code that requires or has objects? (Kind of seems ridiculous if that is the case)
I get an error saying "Cannot use __try in functions that require object unwinding"


Yes, that is a restriction, but only for the function actually containing the __try/__except. A usual solution is to write a main-function which contains nothing but the SEH around a call to another function (for example safeMain) which contains everything you would normally put into main.
Yeah I only have a single __try in the whole engine, internally the crash dump code.

Also, one of the reasons that you should try to make invalid code crash is so you don't need crash dumps :D

If the code crashes very reliably and predictably, a programmer can easily reproduce a bug while their own debugger is attached by following the steps provided by the bug reporter.

This topic is closed to new replies.

Advertisement