Jump to content
  • Advertisement
Sign in to follow this  
VanillaSnake21

C++ Best way to catch programming errors in debug builds

Recommended Posts

I'm doing a WinAPI/DX10 framework rewrite and this time around I'd like to start handling errors a little more seriously in my code. Up to this point I've allowed the code to fail naturally and catch the exception within VS. I always found that to be the quickest method. Usually a break occurs and I just step through the call stack and find the problem in seconds (hopefully). However I'm not really sure that's the proper way to do this, but it does feel like I can avoid writing code that is littered with FAILED() or if( == NULL) statements. So what is the proper way? I've read a few other threads on here that talk about using asserts, but I've never really seen any code written with that so I'm bit doubtfull. Most of the conditions I'd like to monitor for errors are usually DirectX function return values, sometimes null points, sometimes WinAPI function fail handles, sometimes index overrun errors, but mostly DX function fails. Again, I feel like debug DX libraries are very verbose and do a wonderful job interacting with VS as it is, so up to this point I've just relied on output window messages for that, but again I'd like to know if that is a proper way to do things.

 

P.S the framework is really just a learning tool for me so it's not going to see serious use. So I lean on the side of cleaner, more readable code vs obfuscated one, so I'd like to not go too crazy with error checking however I'd like to get at least an idea of the way things are done properly. 

 

P.P.S I'd also like to mention that I am aware of exceptions however I'm particularly asking about catching programming errors, I was under the impression that exception handling is more of a run-time error detection method to catch things that are not really the programmers fault (like memory, harwdware or network issues).

Share this post


Link to post
Share on other sites
Advertisement

I would suggest unit testing and code review may be your friends, if you didn't use them yet.

If you are talking about how to handle the system (Windows, DX) API failure, then you should determine how to do it before you code it rather than debug after the failure happens. So,

1, If the failure should not happen (such as failed to allocate the memory), display some error message and quit the program.

2, If the failure is allowed to happen, deal with it. For example, if you fails to open a config file, then create it and reopen it.

This is more about your program logical to handle they API failures rather than debugging it.

 

Share this post


Link to post
Share on other sites

Those FAILED and null tests are normal and commonplace.  Good code is very defensive and checks for errors constantly.

Unit tests ensure permanence, not correctness.  Unit tests fail when the behavior changes. Hopefully tests are written in a way that verifies results were correct, but through history many unit tests were written that ensure buggy behavior remains constant.

Exceptions are a fancy way to provide an alternative return path. They have their uses and some languages rely on them heavily. Since you're talking about C++ code, exceptions generally should only be used in truly exceptional conditions. Games written in C++ should assume that failure is always an option. Reads and writes can always fail, acquiring resources and always fail, anything touching external systems can fail.

If you write code as though every function can (and will) potentially fail, your code will be more robust because of it.  Exactly how you handle each error is up to the program's design.  If you are creating a system, make sure your interfaces are designed that failure is always an option, so failures can be signaled properly upstream. 

If you decide that exceptions are the best way to signal those conditions, and you're comfortable paying the runtime cost of exceptions, they can be a viable solution.

The most common pattern over the decades is a return value indicating success or failure through an error code. The C++ language is adapting more to push better developer behavior in validating success and failure with attributes like [[nodiscard]] in C++17, but even so, correctness and robustness are up to the individual programmers.

Share this post


Link to post
Share on other sites
On 12/9/2018 at 6:20 AM, VanillaSnake21 said:

I've read a few other threads on here that talk about using asserts, but I've never really seen any code written with that so I'm bit doubtfull.

IMHO the presence of assertions is the biggest difference between code written by beginners and advanced programmers.
Assertions act as documentation for each of the assumptions that you were making when writing the code, plus documentation and checking of any pre-conditions and post-conditions of a function (which otherwise aren't formally specified in languages like C). Assertions then also check that all of your assumptions are correct and invariant are adhered to. IMHO, they are vital in any kind of serious code.

On 12/9/2018 at 6:20 AM, VanillaSnake21 said:

P.P.S I'd also like to mention that I am aware of exceptions however I'm particularly asking about catching programming errors, I was under the impression that exception handling is more of a run-time error detection method to catch things that are not really the programmers fault (like memory, harwdware or network issues).

Yeah I'd agree with that. I'd also point out that exceptions are designed for cases where you know that the next bit of code to run (flow control choice) is going to be higher up the stack than your immediate caller --  things that will abort some kind of larger operation. e.g. a file IO error will likely abort an entire save-game loading operation, and not just the function that was trying to write one field into the save file.

On 12/9/2018 at 6:20 AM, VanillaSnake21 said:

However I'm not really sure that's the proper way to do this, but it does feel like I can avoid writing code that is littered with FAILED() or if( == NULL) statements.

For D3D in particular, there are some functions that can fail at any time for any reason. e.g. Present can fail because the user physically removed their GPU.
In D3D9 there were a LOT of functions that would only fail if you passed them incorrect arguments (i.e. a programming error) -- these are the kind of ones that you should handle with assertions instead of error-handling code. If your code is correct, then they can't occur.
In D3D10/11, most of that category have changed to a void return type instead :)
There's still a few, such as CreateBlendState, etc, which can only fail due to programming error (bad arguments) or out of memory, which is unlikely to happen in practice, and to which your only option really is to crash anyway...

So, if you know that a function can't fail as long as you've satisfied all of it's conditions, then check for error using assertions (which act as documentation of those conditions, and runtime validation of correctness).
If you know that a function can possibly fail, then you've got to pass that up the chain...

For D3D, most failures (besides programming errors) are that the GPU was removed, the GPU driver was rebooted, or your ran out of RAM... You could be lazy and just quit in all these cases, popping up a dialog box telling the user that something went wrong :D
...but on dual-GPU laptops, I have actually seen the "Device physically removed" error occur when the driver has decided to switch from the Intel to NVidia GPU moments after starting the game... so it would definitely be useful to handle it gracefully.

My personal philosophy is to write most of your code in a way that there are no failure conditions. Be strict about what the pre/post-conditions and other invariants of each of your functions are. Validate/document these invariants with assertions everywhere. If something has a "failure" / "error" case, treat it as just another unique program feature / branch, not some kind of uniform items that needs a singular, common one-sized-fits-all "error handling" methodology applied to it. Just say no to "error handling". Say yes to features :) 

My other philosophy around programming errors, is that if you detect one, you should not try to "handle" it and allow the program to limp onward. You've just discovered that the rules of the program are not being followed - invariants are being violated! At this point, everthing is up in the air (especially in an unsafe language such as C -- memory corruption at this point means the program could do anything next)... So the best course of action is to halt the program and exit quickly. On your way out, flush the program's log to the disk, save a minidump file, and pop up a dialog box asking the user to email the log and minidump to you, so that you can fix the programming error.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!