Have an amazing solution built in RAD Studio? Let us know. Looking for discounts? Visit our Special Offers page!
C++CodeHow-To'sModernizationRAD Studio

My code used to work but crashes! Easily Solving Incorrect Code with bcc64x

We’ve had a couple of support requests that are along the lines of, ‘I’m upgrading to the new Win64 Modern C++ toolchain. But when I run my app built in Release mode, it crashes, and it’s hard to see why.’

It is hard to see what’s going on from that error, for sure! But to be clear it is not a bug in the new toolchain, as you might wonder, and it’s easy to resolve. Let’s explain what’s going on in all the cases we’ve seen — and how to solve it.

Undefined Behaviour

Each of the cases we’ve seen is the result of the same thing: accessing an uninitialised variable. Here are two examples:

Older versions of Clang, plus the classic compiler, were forgiving here. The code ran. If the variable was on the stack it had an effectively random value (and that’s why the bool if-check would appear to run correctly, there’s a decent chance it always had a non-zero, ie non-false, value.)

But code with uninitialized data is indeterminate behaviour. And the compiler is allowed to do anything: here, an indeterminate value or a ‘trap’. (Spoiler alert: it inserts a trap statement, thus the crash.) Newer Clang works really hard on optimisations, and it needs to know the code it is running is correct. Sometimes the compiler does unexpected things: in the past, you might have read how some compilers could ignore an if statement that checks an uninitialised variable and always execute its body. If you expect that if statement to usually be executed, you might never realise that the if is actually not present in compiled code. That was the state ten years ago; as we’ll see, there’s more today.

Metaphorical picture of accessing an uninitialized variable

Metaphorical picture of accessing an uninitialized variable

But let’s look at the second example. Here, your code will crash. Yet note that you are not dereferencing the uninitialised (ie dangling) pointer. If you dereference a random value, sure, you’d expect a crash most of the time. But this isn’t doing that. Yet your code, compiled with bcc64x in Release mode (optimisations on), will consistently crash in this scenario.

And, and this might be the most unexpected thing, Clang does this for the first example too. Yes, this code:

will crash at runtime when compiled with optimizations on.

Why? Here, Clang is a bit more strict than other toolchains and it achieves this by making indeterminate behaviour not work… it inserts a trap statement, ie deliberately crashes. I don’t have insight into the internal Clang decisions here, but it is likely that this is a direct way to enforce in the optimizer that if code is reachable, it is valid.

On the other hand if you have a million lines of code and scattered in there are three variables that were not initialised, it’s not helpful to see your code crash and have no idea why. Because unless you follow the steps below it’s not obvious.

Solving it: easier than expected

Luckily it’s really easy to find this behaviour ahead of time, making it really easy to solve.

Clang can emit a compiler warning on uninitialized variables, but for some reason, doesn’t do it by default. I personally believe we likely should have caught this before release and gone against Clang’s default behaviour, because it is more helpful to warn than to just crash. (I think Clang should do this by default too.) In general we don’t change default behaviour without a good reason, and we tend to trust Clang has good defaults, and that’s why we didn’t — but in retrospect, assisting you to avoid unexpected app crashes counts as good reason! So I’ve asked our team to enable this by default in 12.2 (note: normal GA disclaimer applies.)

There are a number of warnings you can turn on, but we recommend simply:

That’s it: compile with all diagnostics, and check them. 99% of the time the compiler is right when it gives a warning. 1% of the time, just disable that warning in code via a pragma :

If you see a warning about an uninitialized variable, that is really a warning that with optimizations on that code will crash at runtime.

The correct code is to always initialise variables:

In these code samples, the previously uninitialized variables are now initialized to false and nullptr respectively.

This should be done for all variables: not just locals as in these code snippets, but global variables, fields, etc. Everything.

Benefits

When you get your code to compile without warnings, you can feel much more confident in your app’s runtime behaviour. If your code has never had this done, you may find a large number. At the very least, compile it once and scan through, even if you don’t fix all of them. But do a once-over check at least. We strongly recommend your code, regardless of the compiler it uses, should build without warnings.

(Also, oddly enough, in some cases the more warnings emitted the slower compilation can get. So while it’s situation-dependent getting rid of warnings can also speed up your build speed.)

In addition, this allows the compiler more freedom for optimisations. And we all like both correct and fast code!

 


Reduce development time and get to market faster with RAD Studio, Delphi, or C++Builder.
Design. Code. Compile. Deploy.
Start Free Trial   Upgrade Today

   Free Delphi Community Edition   Free C++Builder Community Edition

About author

David is an Australian developer, currently living in far-north Europe. He is the senior product manager for C++ at Idera, looking after C++Builder and Visual Assist.

1 Comment

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

IN THE ARTICLES