Sign in to follow this  
Super Llama

[C++] Strange behavior only found in Release mode

Recommended Posts

Hey guys, I've been working on a complex project for a while now, and it's gone pretty well. I'm at the point in the project where basic GUI's can be drawn with a special kind of 2D scene graph (100% custom code, using d3d9 for the renderer) and now I'm experiencing extremely weird behavior in my binary. Specifically, whenever I launch the project in Debug mode, everything works perfectly. When I compile the Release configuration and run it, however, there's a 50% chance for the project to start without drawing any sprites. It draws the text fine, but not the sprites. I've done a lot of checks and things, but it's kind of hard to do when the only place you experience the problem is a place outside the ability of the built-in debugger. The function loading the textures is returning D3D_OK, so I think it's at least loading the textures correctly.

To make matters worse, the problem also never occurs when I run the release config through the debugger and ignore the "no debug information available" message. I probably made some bad coding decisions-- I like to use void* casts a lot. Is that a common cause of optimizer problems? Earlier I had an equally unusual problem where a certain private variable assignment simply didn't work at all, and breakpoints placed on the line promptly moved to the line below it when I compiled. I solved it by, oddly enough, moving said variable definition up prior to the definition of a different int. Yeah, my response exactly.

I wonder if changing my c-style pointer casts to dynamic_cast would help the matter? Or could it possibly be a problem with the way I initialize D3D? Hope you can shed some light on this. VC seems to do this kind of thing a lot to me.

Share this post


Link to post
Share on other sites
The vast majority of debug vs. release issues are because you forgot to initialize a variable. Make sure ALL of your variables have a starting value, even if it is null.

theTroll


Share this post


Link to post
Share on other sites
[quote name='Super Llama' timestamp='1298223062' post='4776729']
[...] and now I'm experiencing extremely weird behavior in my binary. Specifically, whenever I launch the project in Debug mode, everything works perfectly. When I compile the Release configuration and run it, however, there's a 50% chance for the project to start without drawing any sprites.
[/quote]
Initialize everything before use. Add plenty of asserts. Ideally also write tests, at least for the hairier parts of your code.

[quote]
To make matters worse, the problem also never occurs when I run the release config through the debugger and ignore the "no debug information available" message. I probably made some bad coding decisions-- I like to use void* casts a lot. Is that a common cause of optimizer problems?
[/quote]
No, but a common source of correctness problems.

[quote]
Earlier I had an equally unusual problem where a certain private variable assignment simply didn't work at all, and breakpoints placed on the line promptly moved to the line below it when I compiled. I solved it by, oddly enough, moving said variable definition up prior to the definition of a different int. Yeah, my response exactly.
[/quote]
Sounds like you have some rogue code that is scribbling on your memory at some point, or perhaps that your build is faulty.

[quote]
I wonder if changing my c-style pointer casts to dynamic_cast would help the matter?
[/quote]
Ideally, avoid casting as much as possible and when you do decide that you need to cast, use the *least* permissive kind. I tend to avoid C-casts because they might end up doing more than you intended without you realising.

[quote]
VC seems to do this kind of thing a lot to me.
[/quote]
Don't start blaming the compiler. Suck it up and start facing the fact that you have a bug. Again:

[list]

[*]Initialize everything before use.
[*]Add plenty of asserts.
[*]Ideally also write tests, at least for the hairier parts of your code.
[/list]

Share this post


Link to post
Share on other sites
Good idea on the asserts, I forgot they existed. I'll add a few and change my casts. I rely on casts to provide a degree of ambiguity to my render wrappers.

I feel that blaming the compiler is at least partially appropriate, since the debug build which differs only in project properties is not exhibiting this behavior at all. How often do "faulty builds" occur? Should I try a mere rebuild when these kinds of anomalies occur?

EDIT: Somehow I missed the first reply before posting this. I'll check for uninitialized variables too.

Share this post


Link to post
Share on other sites
[quote name='Super Llama' timestamp='1298225509' post='4776738']
I feel that blaming the compiler is at least partially appropriate,
[/quote]
I'm afraid it almost never is, say 99.99% of the time.

[quote]
since the debug build which differs only in project properties is not exhibiting this behavior at all.
[/quote]
If your code invokes what the C++ standard calls "undefined behaviour" the compiler is allowed to do whatever it likes. I am 99.99% sure that this is what your code is doing :)

Example: in debug builds, it is quite common for a compiler to initialize pointers and integers to 0. This is not required by the C++ language and so in release builds, this initialization may be elided. This is why both TheTroll and I suspect uninitialized variables.

[quote]How often do "faulty builds" occur?[/quote]
Depends on how you're building your code (makefiles, vcbuild, etc).

[quote]Should I try a mere rebuild when these kinds of anomalies occur?[/quote]
It should be near the end of your list of things to try. Incorrect code is orders of magnitude more likely, I'd say. But if your build system assumes that your version of boost (for example) hasn't changed between builds when you have in fact updated it, this can lead to all kinds of strange behaviour (due to ODR violation perhaps -- another instance of undefined behaviour).

Share this post


Link to post
Share on other sites
Wow, that was really an easy fix. Thanks to your input I checked for uninitialized variables, and sure enough the code I used for rendering sprites relies on a nesting variable to make sure that any instances of "Start2D Start2D End2D End2D" will only call for the first and last instances-- and it wasn't initialized at all.

Thanks again, knowing that debug mode initializes variables to null will be extremely useful later on-- I always kind of assumed that debug and release differed only in the level of code optimization.

Share this post


Link to post
Share on other sites
You use dynamic_cast to convert a pointer up or down an inheritance hierarchy and nothing else. The dynamic_cast will set the pointer's result to 0/NULL if the cast did not succeed. For example, if you cast a pointer to Shape to a pointer to Circle, but the object pointed to is actually a Square, it will give 0. You can check this and change your behavior accordingly. If you use the other cast types, it can force to use the pointer for the wrong object without any checks, so learning each of the cast types and when they should be used is a good idea. However, frequent reliance upon dynamic_cast implies you have a design problem. You are meant to use virtual functions to vary behavior for OOP.

Generally speaking, static_cast is for numeric conversions (int <-> float), reinterpret_cast is for possibly dodgy conversions (convert a 2d int array to an int pointer for example) and dynamic_cast to find out what's on the other side of an OOP pointer. C-style cast may compile reinterpret_cast, so it can be dangerous unless you are 100% sure of what is on the other end.

I have never yet come across a situation in C++ where I could not find an alternative to the use of a void pointer (apart from using malloc of course). If you think carefully about the problem, you can usually come up with another way. Consider a base class which can do everything you may ask of what's on the other side of the void pointer using virtual functions, and then derive classes for each possibility.

When in release mode, look in project properties. You may be able to turn uninitialized variable checks back on.

Share this post


Link to post
Share on other sites
In some cases I've found that certain optimization options break code; it's rare, and usually means you're relying on some hack such as an example from my case of using a library that translates signals to exceptions and messes with the instruction pointer etc. Other cases are where you use the assume no aliasing option but there is aliasing in your code.

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