Jump to content
  • Advertisement

Recommended Posts

I've reading about game/engine initialization both in Game Engine Architecture and Game Coding Complete. I have understood there is a problem with C++ and initialization, because initialization of global variables is non-deterministic, and the order your initialize modules of engine is critic since some modules depends other modules. They explains some approaches to avoid the undefined behavior, but I wonder something. Why must I initialize modules like global variables? Why don't initialize like members of Application object? Maybe last would lead to annoying scope issues, but, why don't initialize in main() scope? Would they have "global" scope in main() and deterministic initialization?

Thank you and sorry for my English.

 

Share this post


Link to post
Share on other sites
Advertisement

There's no good reason to not initialize things from main directly. Typically people try to build complicated systems that rely on running code from constructors inside of global or static objects. After trying a few out and writing some myself I personally decided they are unnecessary and not worth the trouble.

In my own code I just call functions from main in a deterministic and simple way.

Share this post


Link to post
Share on other sites

The main reason for initializing things outside of main is that they are needed for other things that also exist outside of main.  This sounds like a circular argument, but consider that every static variable has a lifetime that extends beyond main.  If one of your functions has a static variable of type T, and T accesses the logging system in its destructor, then the logging system and its dependencies need to be available outside of main.

You can, of course, avoid this whole issue by not using any static variables.  However, this requires a lot of discipline, and it has costs both in code complexity and performance.

Share this post


Link to post
Share on other sites

Using static variables doesn't imply that they must have a lifetime longer than main.

Share this post


Link to post
Share on other sites
12 hours ago, midn said:

Using static variables doesn't imply that they must have a lifetime longer than main.

Um, yes it does.  In C++, static variables guaranteed to survive past the end of main.  That is how the language works, and there's nothing you can do about it.

You might be thinking about cases where you set the static variable to some harmless dummy value, such as nullptr for pointers, at the end of main.  You can of course do that, but that doesn't change the essential fact that the variable itself has a lifetime that extends beyond main.  It also requires knowing where all of your static variables are, which can break encapsulation and/or require writing extra boiler-plate code, especially for function-static variables.

Share this post


Link to post
Share on other sites
Posted (edited)

Correct me if i am wrong because i am not an expert, but this is only the case if they where actually constructed in a specific run, right ? I mean, it may be the case that a piece of code declaring a static variable (edit: outside of main) might not be executed because it was never reached. But if it was executed, then the static stuff will live until the program terminates.

Correct ?

Edited by Green_Baron

Share this post


Link to post
Share on other sites

Yes, static variables that are never constructed are also never deconstructed - but in that case, the variable doesn't really "exist" (i.e. have a lifetime) at all, so my short statement is still basically correct.  The rules for determining which static variables actually get constructed, in which order and at which point in the program, are actually quite complicated with lots of special cases to consider.

Share this post


Link to post
Share on other sites

Static variables are necessary at least at the point of singletons, fixed size buffers and one-time startup information you don't want to calculate every time again. Examples are a log or profiling buffer or UTC time. Whyt I usually do is wrapping the engine entry point into a macro calling some start-/ and cleanup code arround the user main.

What you generaly want is also reading some kind of config destinated to your game from a file in the game directory like the "engine.ini" file to set specific configuration to your engine or have a "userprefs.ini" that set local user settings like display resolution

Share this post


Link to post
Share on other sites
Posted (edited)

There are some subtleties here.

Quote

I have understood there is a problem with C++ and initialization, because initialization of global variables is non-deterministic

Global variables are not all static variables and vice versa. It is perfectly possible to have a static variable in a function that gets initialized upon first usage (this is more complicated than you'd think, especially with multi-threading in mind), which then is not a global. I think this is what user "midn" refers to:

Quote

Using static variables doesn't imply that they must have a lifetime longer than main

That said static globals do imply that lifetime is longer than main as per definition they get 0 initialized when the executable is loaded. They usually live in their own segments (.bss or something like that) To try to answer your question more directly:

Quote

Why must I initialize modules like global variables? Why don't initialize like members of Application object?

What you are saying is a perfectly viable solution. However as your program grows in size you will run into problems where the application object will have a lot of references to other things. If you were to use 3rd party libraries, then there would be no way for your application object to 'own' those references. 

To that end people have tried to use global variables (non static as static linkage hides the symbol for external linkage) and then you get the initialization order problem. If you have two variables A and B, and you know A needs to be initialized before B, then easy. But if you have 100+ variables, then it essentially becomes a graph. 

And this is generally where people reach for the singleton pattern to 'solve' their problems https://en.wikipedia.org/wiki/Singleton_pattern. (Side note for C++ is that using pointer statics and using new/malloc for initialization in the singleton will cause memory leaks upon shutdown. Which isn't a problem if the process terminates, but you should probably register an atexit function to free the memory if you were to use CRT debugging facilities to track memory leaks: https://docs.microsoft.com/en-us/visualstudio/debugger/finding-memory-leaks-using-the-crt-library?view=vs-2019. I mention this because it is often overlooked.)

The problem however with singleton patters is that if the singleton itself depends on another resource then you create a dependency chain. It'll work, but the main objection is lack of control of order of initialization. E.g. if you have a RenderSystem singleton that needs to load some files using a FileSystem singleton, then when you were to call RenderSystem::GestInstance() it would have to instantiate the FileSystem singleton. This in itself is fine, but becomes problematic if you want to control the order. The order is usually controlled because some system needs to be configured. For example the FileSystem could set a file search directory based on which map is loaded, etc. And because you can't set those settings before main runs (well you can with a custom linker entry point, but that's a whole other story) you are now back to the original problem or initialization order.

To add to this problem is that C++ doesn't have a very portable way of forcing initialization order. Clang/GNU/MSVC all have their own unique ways of controlling static initialization order. So how to solve this?

Well the most common method is to not use lazily initialized singletons, but rather in main call say

FileSystem::CreateInstance(settings)
RenderSystem::CreateInstance(settings)

// Now do actual things

But now you essentially might as well have used global variables. Which is what you're saying:

On 5/24/2019 at 2:17 PM, Goyira said:

why don't initialize in main() scope? Would they have "global" scope in main() and deterministic initialization?

And this is generally what happens... globals initialized in 'main' (or some other top level function) for things where the order matters. And lazily initialized singletons for things where the order does not matter.

Edited by deadc0deh

Share this post


Link to post
Share on other sites
On 5/25/2019 at 12:34 AM, Randy Gaul said:

There's no good reason to not initialize things from main directly. Typically people try to build complicated systems that rely on running code from constructors inside of global or static objects. After trying a few out and writing some myself I personally decided they are unnecessary and not worth the trouble.

In my own code I just call functions from main in a deterministic and simple way.

I second this. The few things I initialize statically include my type library, memory manager and any singletons that deal with system-wide marshaling (of which there's one or two). The assumption here is that these are somehow interdependent.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!