开发者

How to wait until dynamic initialization of static variables finishes

开发者 https://www.devze.com 2023-03-10 03:24 出处:网络
According to information on other sources C++ distinguish two kinds of initialization of static variables:

According to information on other sources C++ distinguish two kinds of initialization of static variables:

  • static - if variable is initialized by putting it within initial value to special section in executable file.

  • dynamic - if initial value of static variable has too be computed

There is many discussion regarding order of dynamic initialization constructors calls. But I didn't found info how to wait until all dynamic initialization of all static variables in executable finishes. Or from other side how to call by hands this initialization in the indirect/generic way.

I use static variables initialization for loose coupling of kind of plugin architecture. I have plugin1.c, plugin2.c ... and static variable inside of plugin1.c static bool installed = plugin1_install();

But in main I need t开发者_如何学JAVAo wait until all plugins installed.

The same thing I use was suggested here 1. As the answer to following question

I wanted to write a shared library for my program. However, I need the library to have some self initialization routines before anyother functions in the library are called...

Answer:

C++ itself supports global initializations of things. You can do things like:

int global_variable=some_global_function();

This would be illegal in C, but is legal in C++.

Can I implement feature I need with help of __CTOR_LIST__?


There is no need to wait. In C++ program startup is singly-threaded so once you get to the first instruction of main all global static storage duration variables will have been already initialized.

The problem you read about is however that there are little guarantees about what is the order of initialization during that startup phase. In other words if the initialization of a static object requires the use of another static object then you're possibly in trouble.

My suggestion is try to avoid doing complex processing during the startup phase... and specifically avoid doing anything that could possibly fail. The reason is that during that period (and during its dual at shutdown time) the system is not yet (or not any more) 100% functional and debugging is especially hard. For example in windows systems a segfault during shutdown is often silenced and with some environments debugging is not working properly before the start of main.

If your initialization phase is just however plugin registration then this can be made safe by using a lazy singleton pattern for the registry:

struct Plugin
{
    Plugin(const std::string& name);
    ...
};

std::map<const char *, Plugin *>& registered_plugins()
{
    static std::map<const char *, Plugin *> directory;
    return directory;
}

Plugin::Plugin(const char * name)
{
    registered_plugins()[name] = this;
}

...

struct MyPlugin1 : Plugin
{
    MyPlugin1() : Plugin("Plugin-1")
    ...
} MyPlugin_instance;

In the above code MyPlugin_instance variable will be created during startup (before main) but the plugin registry is known to have been already correctly constructed because it's not a global but a function static, and those variables are initialized the first time their scope is entered.

Declaring instead the global plugin directory as a static duration global would be problematic because if plugins and the directory are not in the same compilation unit then there is no guarantee about the order of initialization; therefore it could be possible that the plugin gets constructed and tries to register itself in a map that is not yet constructed.

What could still be dangerous with this approach is however accessing the singleton in the destructor of a static duration object because - like for construction - the risk is that someone will try to use the registry after it has been already destroyed.

My suggestion is anyway to try to keep this kind of pre-main processing at a minimum; anything non trivial can be a source of big and hard to debug problems. Startup and shutdown, if possible, should be IMO kept under clear control and singly threaded.


You can't do it the way you are doing it. I've done this before in C++ and there are a few problems due to the way C++ works that you don't have in other languages.

First of all, there is no guarantee of order of initialization. Someone already copied and pasted the statement from the standard, and this is basically what the copied paragraph means.

So the basic approach I took was to use a prototype pattern. For purposes of following the open-closed principle, you don't want to have a piece of code that you have to keep modifying each time you add a plugin. So the plugins need to either register themselves (which is hard) or you have some startup class that loads them from shared libraries (which is quite a bit easier). The approach you take depends on what your requirements are. If you are familiar with the prototype design pattern, the rest of this might make some sense.

It looks like you're leaning towards them registering themselves since your making your plugins as part of your code. I would highly suggest taking the approach of putting each plugin in a shared library rather than do it this way (will explain why in a moment). Using shared libraries, you can have one class load shared libraries from a directory list and probe them for your plugins. If this is done on initialization, then all the plugins are loaded before any part of your program uses them.

To make plugins register with the prototype manager upon program startup, the prototype manager needs to be a singleton. You can make a class that performs the registration for you, and each plugin class' file can define an instance of that registration class. When you add more plugins, they automatically register as long as you make an instance of that registration class as a global variable.

Now here is the hard part. If you don't have an explicit reference to an instance of any of your plugins, it may be optimized out of the code on compile when compiling an application (depends on the compiler). The reference to it when registering with the prototype manager is not enough; there is no direct call to an instance of your plugin, so the linker will not link code that is not called. I worked around this by containing all the plugins and the prototype manager in a shared library that was linked to the application. If it is in a shared library, the linker can't optimize it out because it doesn't know if a particular class will be referenced by code using that shared library. (Thus the suggestion to go to a shared library per plugin.)

There may be a way to force a reference to each plugin through compiler settings. I had to do this for an iPhone app in Objective-C. I don't like this because it means you have to remember to add the reference in the compiler settings every time you add a new plugin. In my opinion, this does not follow the open-closed principle even though you're not modifying code.

This would work with function pointers too (since you might be doing that for some reason). It would take a bit of modification to the way the prototype pattern is created.

I'm sure this is clear as mud. =) Hopefully you go with the one-shared-library-per-plugin approach and make it easy on yourself.


According to ISO14882-2003 (3.6.2):

  • Dynamic initialization of static duration objects of namespace scope (including global namespace scope) is performed in order of objects definition in a single translation unit. Order of initialization of objects from different translation units is unspecified.

  • Some particular dynamic initialization of namespace scope object may be performed either before the first statement of main function or it may be delayed after the first statement of main. If it is delayed then it is guaranteed that the initialization is performed before the first use of any function or object defined in the same translation unit as the object being initialized.

ADD:

Obviously to make sure that some of your static duration objects of namespace scope is initialized you just have to either call any function or use any object defined in the same translation unit (as the object you want initialized) in function main or in any function directly or indirectly called from the function main.


Unfortunately the language does not support those semantics.

static storage duration objects SSDO (both static and dynamically initialized) are only guaranteed to be initialized before they are used. The standard has a description of the ordering constraints and guarantees in [basic.start.init].

The following describeshow dynamic initialization may be deferred until after main.

(n2723) 3.6.2 Initialization of non-local objects [basic.start.init]

paragraph 4

It is implementation-defined whether or not the dynamic initialization (8.5, 9.4, 12.1, 12.6.1) of an object of namespace scope with static storage duration is done before the first statement of main. If the initialization is deferred to some point in time after the first statement of main, it shall occur before the first use of any function or object defined in the same translation unit as the object to be initialized.

In practice, as long as your object have no side affects, then you can write your code as if they were all initialized before main (as an attempt to access them will cause their initialization (no explicit action is required by your code)). If they have side affects then don't rely on the side affects happening.

If you rely on the side-effects then you can take advantage of main()s location.
Just declare all SSDO objects in the same file as main(). Because they are in the same translation unit they must now all be initialized before main is entered.

It is written this way (or one of the reasons) to allow the usage of dynamically loading shared libraries (dll) without explicitly having the concept of shared libraries written into the standard. As a dynamically loaded shared library is not present in the executable before main() is entered it can not have its SSDO initialized before main(). The above part of the standard allows for this fact. Though it does guarantee that any SSDO will be initialized before they are used.

Shared Libraries

Of course nothing about shared libraries (dll) is defined in the standard as these components are not tied directly to the language but are a feature provided by the OS.

That being said. Most OS's when they load a shared library (through the standard shared library loading mechanism ie. dlopen()) will initialize all SSDO before the function call to load the shared library returns. In situations where SSDO initialization is deferred it will obey the rule defined above in [basic.start.init].

Order of Initialization:

The order of initialization is not usually important.

Unless you use a SSDO during the initialization (or destruction) of another SSDO. Then order does become important as you need to make sure that the used object has been created/initialized before it is used (otherwise its data is garbage).

You have a couple of options. If the SSDO are in the same translation unit, you can define there initialization order by the order they are declared in the translation unit:

// File OneFile.cpp
myClass2 gv_2   = some_othere_global_function();
myClass1 gv_1   = some_global_function(); // If this uses gv_2 it must be declared first

If they are in different translation units then you have no guarantees about the order:

// File 1.cpp
myClass1 gv_1   = some_global_function(); // If this uses gv_2 you may have a problem.

// File 2.cpp
myClass2 gv_2   = some_othere_global_function();

The order problem can be solved by using a function:

// File 1.cpp
myClass1& get_gv_1()
{
    static myClass1 gv1 = some_global_function();  // This function can only access gv2
                                                   // by calling get_gv_2(). This will of
                                                   // force dynamic creation of the object
                                                   // this it is guaranteed to be initialized.
    return gv1;
}

// File 2.cpp
myClass2& get_gv_2()
{
    static myClass2 gv2 = some_othere_global_function();
 // ^^^^^^
 //    Notice the use of static here.
 //    This means the object is created the first time the function
 //    is called (and destroyed on application exit).
 //
 //    All subsequent calls will just use the same value.
 //    Note the return type. We return a reference to the object.
    return gv2;
}

Also see:

  • c-static-variables-initialisation-order
  • finding-c-static-initialization-order-problems
  • what-is-the-lifetime-of-a-static-variable-in-a-c-function
0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号