Jump to content
  • Advertisement

Archived

This topic is now archived and is closed to further replies.

Russell

Debug code: assert or if-statement?

This topic is 5222 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I usually use assert macros to validate my data at the beginning of a function. I was thinking about this situation. The asserts get turned off in a release build, so I am basically assuming that they will never fail (after being tested for a good while in debug mode), and they are only there to help find bugs more easily when I compile in debug mode. My question is, if I am already assuming that the asserts will never fail, then why not just put in an if-statement and leave it active in release code? My thinking here is that since it can be assumed that the asserts will never fail, the branch prediction will have no problem at all predicting the correct branch in the corresponding if-statement, so there is basically zero overhead involved (assuming the asserts were simple, as in the examples below), and even though the asserts should never fail, it would be nice for the code to do something besides halt the program in the rare event that an assert would have failed. Here is an example. Normally I might do something like this.
int SomeFunction (int number)
{
    assert(number >= 0); // <-- arbitrary asserts
    assert(number <= 9);
    // code here...
}
I am considering replacing that with something like this.
int SomeFunction (int number)
{
    if ((number >= 0) && (number <= 9))
    {
        // code here...
    }
    else
    {
        // report error, return a special value, or whatever
    }
}
The first function looks a little cleaner to me, but I can think of situations in my code where I have used asserts, and even though the asserts never fail, there is a reasonable alternative to halting the program in the event that the asserts did fail. Maybe I am just using asserts where I should be using if-statements or exceptions. What is the correct way to handle this?

Share this post


Link to post
Share on other sites
Advertisement
if statements will always take a slight amount of cycles to determine if it occurs or not. Unless the number you''re testing is constant (which it isn''t), the compiler can''t throw away the if statement. If you are CERTAIN that in release mode everything will be worked out and that the variable will ALWAYS be correct unless it was an error by the PROGRAMMER and not the user, then use assert. If, however, there is a chance that the client will put in an invalid value, use an if statement.

Personally, I never use an assert statement, and prefer to program as if it were to be released and that the programmer is the user, so I waste a few cycles on ifs that might be able to be done without.

Share this post


Link to post
Share on other sites
Here is how I do : I use my own customized assert. For instance I am developping an open source math lib. I call it xAssert(). I can give you some code advices if you want ot do it but you're dizzied with the ANSI C preprocessing language.

This way responds to your concerns :

it would be nice for the code to do something besides halt the program in the rare event that an assert would have failed
- you can add variants. My xAssert calls xBreakpoint, thus it opens the debugger, I can rewind the call stack and watch which functions and variables made it fail. You could directly create a macro function called xBreakPointIf().

they are only there to help find bugs more easily when I compile in debug mode.
- I can set a macro xFORCE_ASSERT which leaves asserts active even in release. There are subtle things

BTW you are right to debug a lot but you should always test your code in release right after any debug session is done. Do it often else you might fall on a big bone one day. Some compilers have nasty bugs that appear only in release. And I don't mention your own potential errors and the side effects of #if NDEBUG.


Now other comments :

so there is basically zero overhead involved
- There you are wrong. Any if() testing non predictible input params won't be removed as dead code. There is not only branch prediction to take into account. The test is always be executed. For instance if( sin(alpha) > 1.0f ) will always call the lengthy sin(). I could mention AGIs, paging, etc... many things that will kill performances in small functions, disable some inlines, ... and for instance many things in this overhead that would be inept in my high speed math routines.

- Don't use C++ exceptions if you want speed !!! This is catastrophic for optimizations. Frankly I hate them. I think softs based on exception handling are basically a cheap way for the coder and costly way for the CPU not to design the software architecture well.

- If then statements should be reserved for cases that can be handled directly by the callee or by the parent caller if it's your own stuff. Else it's a propagation of unmanageable unknowns. Better assert conditions, specially if the inputs come from your own functions.

- You can use a strategy to avoid side effects with the user interface. But if your interface is bulky, side effects should be very limited.

- Use the cheap asserts for your own code. Predicates, postconditions compose a very clean error handling and constitute a base of proven solid languages like EIFFEL. (Well don't ask more me about EIFFEL, I am a low level coding biased, asm maniac and C++ is the highest level I can bear apart W3C and perl stuff.)


[edited by - Charles B on June 5, 2004 4:45:14 AM]

Share this post


Link to post
Share on other sites
The asserts are for conditions which should never arise, such as being passed a null pointer when the function then goes ahead and dereferences said pointer. Usually these situations are reserved for programmer errors that shouldn''t be ''fixed'' at runtime in the same way that an if() statement can do. VS has a VERIFY() macro that stays in release mode I believe.

Contrast that with exceptions, which give you a way to fix failures at runtime, and in a different way -- particularly when the range of your return value can''t contain an error code. Proper use of exceptions can contribute greatly to code maintainability and robustness, both of which are far greater virtues than speed.

Share this post


Link to post
Share on other sites
Asserts don''t have to be trivial tests though, often when writing a new data structure or class its nice to chuck in sanity tests all over the place to see where it breaks (think of it as on-the-fly unit tests).

So if I was writing a linked list type object, then before/after every important operation you can do something like checkValid() which makes sure you''ve not introduced something weird like a loop or duplicate nodes. These tests could potentially be very time consuming, yet by shoving them in an assert I''m constantly testing my code whenever I run in debug mode. Any code changes which break these test gets flagged early and often, rather than silently going wrong to cause untold mayhem.

Share this post


Link to post
Share on other sites
I like asserts during development. They''re a way to prove to yourself that everything you''re doing is being done right. In the event that one fails, it produces a core file. If your if statement fails, the program just exits and you have little to go on so far as how to fix the problem. If your assert fails, you can load the core and executable into a debugger, and you look at the stack to see what went wrong. Plus, once you''re done with development and confident that none of the asserts will ever fail, it''s real easy to recompile a release version without them for truly zero overhead.

With that said and has been mentioned before, you should never use asserts for something that might fail due to anything other than programmer error. For example, this code:

fp = fopen("hello.txt", "r");
assert(fp != NULL)

is bad. Here you need an if statement because your program should try to recover from this error.

Share this post


Link to post
Share on other sites
I was thinking about a situation where the assert is really very trivial. Here is an example. Let''s say I have a class that represents a color in a game of chess (white and black). In the first example, I''ll assume that the color will always be white or black.

enum Color { white, black };
Color EnemyColor (Color color)
{
assert(color == white || color == black);
return Color(color ^ 1); // single bitwise op, very fast
}

Here is a second example that adds a conditional, but it has no assert. I allow for a Color to be noColor also.

enum Color { white, black, noColor };
Color EnemyColor (Color color)
{
switch (color)
{
case white: return black;
case black: return white;
default: return noColor;
}
}

Usually this function will be called in an alternating fassion, i.e., it will return black one time, then white, then black, and so on. The branch prediction will have no problem with this kind of pattern. So will the second version really be detectably slower than the first? I don''t think so (maybe I''m wrong), and there is somewhat of an upside to the second version.

Let''s say that somehow there is a corrupt value for a Color passed to this function. In the first example, the program simply aborts if it is in debug mode, otherwise a crash is probably coming up before too long.

In the second example, the damage is limited somewhat. If a corrupt value gets passed to this function, noColor is returned instead of halting the program, and the caller can handle that and continue running.

This is what I meant when I said that an assert is an okay choice, but there is a reasonable alternative to halting the program.

Share this post


Link to post
Share on other sites
Well, the question is whether or not the error is recoverable or not. The way i look at it, my functions have preconditions and post conditions. If the preconditions aren''t true, that must be due to programmer error of some sort and it''s an irrecoverable error. Therefore, i nearly always put assert statements in to make sure the preconditions are being met. If you''re trying to determine the color of a chess piece and it''s not white or black, there must have been some strange condition that has arised due to programmer error. What can you do with a piece that isn''t white or black? Is there anyway to determine what color it should be? That seems like a fatal bug in a program. It''s not like a system call failed, which can be expected in extreme circumstances.

One thing i use assert for, which has saved me hours of debugging, is using assert statements to avoid deadlock with mutexes. Everytime a mutex is unlocked, i have a wrapper function that makes sure the thread that''s unlocking the mutex is the same as the one who locked in the first place. If that isn''t true, you''re better off quitting immediately because you''re gonna seg fault or deadlock sooner or later anyways. likewise, i make sure the same thread doesn''t lock the same mutex twice without unlocking it in the meantime.

Share this post


Link to post
Share on other sites
quote:
Original post by Russell
In the second example, the damage is limited somewhat. If a corrupt value gets passed to this function, noColor is returned instead of halting the program, and the caller can handle that and continue running.

on that example, the program should quit if the function gets anything else other than a valid color, cause that would be a programmer''s error. so assert is the way to go on this one.

Share this post


Link to post
Share on other sites
Choice ?
Exactly this example is typically a bad example for handling errors dynamically. noColor is a non sense int the context you describe (chess). Even if the caller might be able to cope with it, it simply does not have to be.

The error must be recovered by rewriting the caller code somehow, that is at (re-)compile time. It''s typically an assert of definition domain.

The compiler lets potentially any int be passed as an enum. Your assert is a precondition on the actual type the function is supposed to receive, which must be in your docs or header comments, that is your color enum which only has two values. Here nothing but an assert should check for the definition domain of you functions.

About speed :

Else about speed xor is just a fraction of clock cycle, with no overhead at all if the CC inlines your function. xor eax, ebx.

The switch might be optimized by the CC in three main possible ways, branch, look up table, logical ops. In any case much slower.

I agree this comment is not probablyvery relevant in the case of this chess game function. But it might be for an analoguous example in a different context.

Last : moderation about C++ exceptions.

I admit they can be useful for high level code. But if I had to use them, I would cut my soft into several layers and only the top one, handling the user interface, side effects or some precise internal unstabilities mostly, would be compiled with error handling enabled.

Share this post


Link to post
Share on other sites

  • Advertisement
×

Important Information

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

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!