The ruckus with the Linux build


#1

Fun technical details

When you’re writing software in C or C++, ordinarily program execution starts in a function called main(), and continues in a basically linear order until it reaches the end of the program. Here’s an example:

#include <stdio.h> // contains the definition for 'printf', used later.

int main( int argc, char *argv[] )
{
    printf("Hello, world!\n"); // prints out this string
    return 0; // returns '0' to the OS, to tell it that we completed successfully.
}

That’s a really basic console program which just prints out ‘Hello, world!’. The software starts at the main() function, runs the printf() line (which prints out a text string to the console), and then runs the return 0; line, which tells the OS that we didn’t encounter any errors. You can ignore everything else; it’s not important to this conversation. :slight_smile:

So most junior programmers think about things this way. Code executes in order, one line after another. And it mostly does. Here’s another program:

#include <stdio.h>

int g_cats = 3;

int main( int argc, char *argv[] )
{
    printf("I know %d cats\n", g_cats);
    return 0;
}

Here, we print out a message “I know 3 cats”. (The ‘%d’ means to substitute a number into the string, and ‘g_cats’ is the number we say to use). So far, so good. But maybe we should feel a little uncertain. Notice that we’ve assigned the value “3” to the variable “g_cats”, and we assign it outside of the “main()” function. When does that value get assigned? The rule is that code gets executed in order, inside functions starting from main(). What does it do outside of them?

Here’s a fun little gotcha. One of the things that happens before your program’s main() function gets called is that the program initialises values in the “static data segment”. Basically, these are any variables which don’t exist inside a function. Variables like ‘g_cats’, above. So it’s okay; by the time the computer calls our main() function, g_cats will definitely have been assigned the value of ‘3’, so we’ll print out the “I know 3 cats” string that we expect to see. It’s guaranteed.

Okay. Now let’s get a little more complicated:

#include <stdio.h>

int g_cats = 3;
int g_pets = g_cats;

int main( int argc, char *argv[] )
{
    printf("I know %d cats and %d pets\n", g_cats, g_pets);
    return 0;
}

Here’s the catch. We know that g_cats will be set to ‘3’ before main() is called. And we know that g_pets will be set to the same value as g_cats before main() is called. Both of those things are guaranteed in the C/C++ languages. But what isn’t guaranteed is which of those two things will happen first.

Inside main(), each line is executed in order. But outside of functions, the order in which variables are initialised is undefined. (“Undefined”, in this usage, is technical programmer jargon meaning that the compiler can do whatever it wants, and we, as programmers, don’t get to complain about what it chose to do). We could quite easily start with g_cats having some random value in it, then g_pets being set to that value, and then set g_cats to 3, leaving that initial random value in g_pets. The compiler would be perfectly within its rights to do that, given the code we’ve given it.

This is basically the problem that I ran into which was causing problems for the linux build, in builds 0.14.17 through 0.14.20.

For those who pay attention to the VectorStorm engine, the specific issue was my using the ‘vsEmptyString’ object during static startup. It so happens that the Windows, OSX, and my local Linux builds all just happened to initialise vsEmptyString before the other static items which used it during their startup, but the Steam build did those things in the opposite order. Completely my fault; as a programmer, I’m not allowed to make assumptions about when that vsEmptyString object would be initialised and become a valid object. The only thing I can be sure of is that it’ll happen before we reach main(). And MMORPG Tycoon 2 does a lot of work before it reaches main()!

The only real solve to this problem is either to initialise those static member variables with explicit values:

int g_cats = 3;
int g_pets = 3;

or else to defer the initialisation of g_pets until a time when we know that g_cats must have already been set. Such as inside main():

int g_cats = 3;
int g_pets;
int main( int argc, char *argv[] )
{
    g_pets = g_cats;
    printf(...);

…so… that’s what I’ve done, to get everything working again! (Mostly solve 1, a little solve 2)

Programming is fun! :nerd_face:


#2

I love these little bits of programming\Development insights. keep them coming