Best practice to track down a release-build-only bug under Visual Studio

I’m doing most of my work on Mac/Linux. However I’ve ported an application to Windows, which runs as excepted as a debug build but outputs no signal when compiled as a release build. On Mac or Linux I’d probably start printing some variable states from within the DSP callback with printf to get an idea what’s going on, however a Windows GUI application gives me no console per default.

Before I start to create a console for this purpose (I know this is possible) - is there any quick way or neat trick to debug a release build in Visual Studio I’m not aware of?

You can use Juce’s Logger::writeToLog which will on Windows use the OutputDebugStr API instead of printf. Then if you start debugging (even a release build) in Visual Studio, those messages can be seen in the “Output” pane of VS. Alternatively you can use the DebugView.exe program to view the messages from OutputDebugStr calls. Obviously you should be very careful when outputting messages from within the audio processing thread, things can get clogged up and you may get side effects that just make it harder to understand your original problem.

2 Likes

You can tell Visual Studio to compile a release build with Debug variables.
That means: you can look at the value of variables, even when in Release build.
There is an option for this in the Projucer. See attached screenshot. Or else you can activate it directly from within Visual Studio.

Release configuration only bugs are usually due to uninitialized variables. The Debug runtime zeroes all memory before its usage, but the Release runtime doesn’t and you usually get garbage. Depending on the size of the project, it might not be feasible to inspect every variable creation, but the more you use auto and in-class member initialization, the safer it becomes.

1 Like

Further to this, uninitialised variables on Windows will often be uninitialised on Mac/Linux too, so you might be able to track them down with Undefined Behaviour Sanitizer on one of those platforms.

1 Like

No, it doesn’t zero them, it initializes all their bytes to “0xDD”. So SHORTs become 0xDDDD, pointers are 0xDDDDDDDD etc.

This is international to trigger buggy behavior in debug builds.

That’s not true in the general case. Is it something you can opt into?

With MSVC that has been true since I’ve been using it (VC4) over twenty years ago. I’m not sure about Xcode (so clang/gcc).

Here is some condensed info:

https://www.codeguru.com/cpp/w-p/win32/tutorials/article.php/c9535/Inside-CRT-Debug-Heap-Management.htm

Might be worthwhile to run Visual Studio’s code analysis; it’s pretty good at finding things like uninitialized variables.

You’ll get a bunch of false positives with some of the ancillary libraries included with JUCE like pnglib, so you’ll need to filter the results a bit.

Matt

@reFX - with MSVC you get special values written when you use malloc, but not if you use an uninitialised variable.

auto* i = (int*) malloc (sizeof (int));
assert (*i == 0xCDCDCDCD);   // Freshly initialised memory

free (i);
assert (*i == 0xDDDDDDDD);   // Dangling pointer

int j;
std::cout << j << "\n";   // Prints something undefined

Clang and gcc fairly reliably set the special values to zero. I don’t know if that is guaranteed.

Which Visual Studio version you used for testing this?

int i;
std::cout << i << "\n";

Here it doesn’t even compile on Visual Studio 2017 15.9.11, the compiler immediately notices it’s a use of an uninitialized variable!

If I do a class and construct it on the stack that uses an uninitialized member :

class Foo
{
public:
	Foo()
	{}
	void bar()
	{
		std::cout << std::hex << m_i;
	}
	int m_i;
};

The compiler doesn’t catch that, nor does the debugger during runtime, but I get a consistent output of “CCCCCCCC” out of that.

Check out Magic Debug Values

Rail

VS 2019.

Like it or not, it’s valid C++ so it will compile. I assume you’re building with warnings enabled and “warnings are errors” selected. In this simple case the compiler can tell that it’s an uninitialised variable, but if you make it more complicated then the compiler can miss these things.

Wow, this discussion got quite far since my initial post yesterday :smiley:

Anyway, you all shared a lot of interesting input, but mainly @Xenakios first answer was the solution to my question.

By the way, it turned out that the error was not based on an uninitialized variable but on the mis-use of a third party API which I could indeed spot with some print-debugging :wink:

99% of my non-working release builds were caused by unitialized variables :slight_smile: