I’ve been using C++ for a long time now. It’s been my programming language of choice since about 1998. Before that I used C and Assembly, and before that I used Pascal and BASIC. Even though I’ve been programming a long time and I’m reasonably familiar with the features of C++, sometimes it still causes me headaches. Here’s an example.
Maps in the game are randomly generated – but the generation is deterministic. This means that given the same starting conditions, such as a seed value, map size, and terrain type, the same terrain can be generated over and over. I use this functionality for the tutorial map. There’s no reason to store the terrain when it can be generated and the outcome is known.
However the other day I found that in only the 32-bit release build of the game, the terrain was coming out differently compared to the other compilations. The tutorial then wanted the player to place a building in the middle of a lake…
This is a hard thing to debug, as when switching back to any debug mode, or release 64-bit, the game worked correctly. I spent a few hours working on the worst kinda of debugging. This consists of logging the state of variables as they undergo thousands of state changes and looking for differences between the correct and incorrect versions. I ensured that the random number generator was always generating the same values, and then after several hours I found this line of code. It looks fairly innocent, but it contains is a non-obvious but critical mistake.
position = Vector3(rng.RandFloat(), rng.RandFloat(), 0.0f);
rng.RandFloat() just generates a random number between 0.0 and 1.0. So what’s wrong here? When the code was first executed, the majority of compilations resulted in Vector3(0.241534, 0.645345, 0.0), but in this case of 32-bit release mode, it was generating Vector3(0.645345, 0.241534, 0.0).
There is no sequence point here. In C++ the order of evaluating parameters to a function call is up to the compiler. So while the random number generator picks the same two random numbers as it’s called, the order that the parameters are evaluated in can be different, and this changes the outcome.
Changing it to the following code fixes the problem because the order of operations is explicit.
float randomY = rng.RandFloat(); float randomX = rng.RandFloat(); position = Vector3(randomX, randomY, 0.0f);
This made me feel like a newb. While I was vaguely aware of this type of issue, I expected the compiler to choose left to right or right to left evaluation and stick with it, and not change order due to optimizations. At least now if I ever implement support for more platforms, I can be sure the terrain generation will come out the same on all systems…