Dev Diary #4 (Part 1 of 3) – September-October 2018
Part 1: Uninitialized Variables
In this developer diary we want to give you insight into some of the more fundamental programming issues we are facing with the source code right now, how they affect game stability and performance, and what we are doing to resolve those problems.
This diary concerns itself with fairly ‘low level’ topics, but we will try to explain everything so that non-programmers can also follow and get a good idea about what’s going on.
For the programmers among you – we are simplifying a lot here, so please bear with us. The Guild 3 is written in C++, so please put on your C++-tinted glasses while reading this.
We splitted the story in three parts. This is the first part: “Uninitialized Variables”.
Variables in programming serve a similar purpose to variables in mathematics (which is where most of you likely know the term from): They can hold a distinct value from a certain range of values, and they are used in calculations, and more generally processing, as a ‘placeholder’ for explicit values.
In C++, every variable requires two items of information:
- A type. This tells the compiler (that’s the program that translates the code we programmers type into something that the computer can actually run) which value range the variable can represent. Is this a piece of text? Or is it a whole number? Or does it represent an entire in-game character
- A name. This is then used by programmers to actually access the variable in code. Usually it’s either something short, similar to mathematics (‘x’, ‘i’, etc…), or something more descriptive (like ‘playerCharacter’ or ‘secretSocietyContainer’, etc…).
By typing the appropriate statements into your code you can get the C++ compiler to carve out a tiny slice of memory and label it with the name you provided.
As you can see above, an initial value is NOT a requirement in C++. You can happily declare to the compiler that you want a variable representing a whole number named ‘myWholeNumber’ without telling the compiler which value it has. After all, you might not care about the value that is in the variable at this point in time, and only later assign something to it. However, you can read from a variable as soon as you have declared it. But which value does it have if nothing has ever been assigned to it? Some programming languages initialize all variables to a default value – for numerical variables this is usually zero. C++ does not do this. The value you get when reading from an uninitialized variable in C++ is whatever previously was in the memory location that is now occupied by your variable – essentially, it’s random. Quite frequently it will actually be zero, which can lead to a false sense of correctness.
There are valid reasons for this, which in the end all boil down to a very fundamental mantra of C++: “You don’t pay for what you don’t use”, or in other words, the language will try very hard to not impose extra rules or requirements on your code that would incur extra runtime cost over what you actually wrote.
What it ultimately means is that in C++ you have to take care to appropriately initialize variables yourself, and you have the option to skip initialization if you know you can. Forgetting to do this can result in errors that appear very randomly, and are difficult to track down. An example would be the “winter disco lighting” bug we had a while ago, which ultimately was caused by a very specific color variable not being initialized. We also had several bugs because of this that we caught internally before shipping the builds, like the game suddenly thinking that there are no male children in the game anymore.
What can we do to avoid and fix these issues? The main course of action is of course to not cause any issues in the first place by diligently making sure everything gets initialized properly. There are guidelines and best practices that should be followed, which help ensure no uninitialized variables can creep into your code.
To find existing issues, there are three main options:
- Manually check code. We are doing this whenever we touch a new area of the code, checking if anything has been left uninitialized. This is not really a good option for finding many issues at once, since our code base is very large and manually checking through everything would take a prohibitive amount of time.
- Use static code analysis. This means that we use special tools that analyze the code to find issues like uninitialized variables (they can also help find other difficult to track down issues). For those more interested in the matter, we’ve been using “clang tidy” and so far have had a good experience with it – it helped uncover many issues (you can read more about this tool here: http://clang.llvm.org/extra/clang-tidy/ )
- Use memory fuzzing tools, that intentionally fill memory with garbage values before allowing our game access to it, helping uncover the common case I mentioned above where those issues go undetected because everything is just filled with zero by default. Valgrind ( http://valgrind.org/ ) would be a very popular free option available for Linux, but there are similar tools for windows. We haven’t taken this step (yet).
If our explanation confused you more than it helped, then maybe this Wikipedia article can help, as it explains the issue rather well: https://en.wikipedia.org/wiki/Uninitialized_variable
We will soon continue this dev diary story with “Memory Leaks”. Stay tuned.