C++: When (and how) are C++ Global Static Construc

2020-01-26 05:39发布

问题:

I'm working on some C++ code and I've run into a question which has been nagging me for a while... Assuming I'm compiling with GCC on a Linux host for an ELF target, where are global static constructors and destructors called?

I've heard there's a function _init in crtbegin.o, and a function _fini in crtend.o. Are these called by crt0.o? Or does the dynamic linker actually detect their presence in the loaded binary and call them? If so, when does it actually call them?

I'm mainly interested to know so I can understand what's happening behind the scenes as my code is loaded, executed, and then unloaded at runtime.

Thanks in advance!

Update: I'm basically trying to figure out the general time at which the constructors are called. I don't want to make assumptions in my code based on this information, it's more or less to get a better understanding of what's happening at the lower levels when my program loads. I understand this is quite OS-specific, but I have tried to narrow it down a little in this question.

回答1:

When talking about non-local static objects there are not many guarantees. As you already know (and it's also been mentioned here), it should not write code that depends on that. The static initialization order fiasco...

Static objects goes through a two-phase initialization: static initialization and dynamic initialization. The former happens first and performs zero-initialization or initialization by constant expressions. The latter happens after all static initialization is done. This is when constructors are called, for example.

In general, this initialization happens at some time before main(). However, as opposed to what many people think even that is not guaranteed by the C++ standard. What is in fact guaranteed is that the initialization is done before the use of any function or object defined in the same translation unit as the object being initialized. Notice that this is not OS specific. This is C++ rules. Here's a quote from the Standard:

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 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


回答2:

This is not OS specific, rather its compiler specific.

You have given the answer, initialization is done in __init.

For the second part, in gcc you can guarantee the order of initialization with a ____attribute____((init_priority(PRIORITY))) attached to a variable definition, where PRIORITY is some relative value, with lower numbers initialized first.



回答3:

This depends heavy on the compiler and runtime. It's not a good idea to make any assumptions on the time global objects are constructed.

This is especially a problem if you have a static object which depends on another one being already constructed.

This is called "static initialization order fiasco". Even if thats not the case in your code, the C++Lite FAQ articles on that topic are worth a read.



回答4:

The grantees you have:

  • All static non-local objects in the global namespace are constructed before main()
  • All static non-local objects in another namespace are constructed before any functions/methods in that namespace are used (Thus allowing the compiler to potentially lazy evaluate them [but don't count on this behavior]).
  • All static non-local objects in a translation unit are constructed in the order of declaration.
  • Nothing is defined about the order between translation units.
  • All static non-local objects are destroyed in the reverse order of creation. (This includes the static function variables (which are lazily created on first use).

If you have globals that have dependencies on each other you have two options:

  • Put them in the same translation unit.
  • Transform them into static function variables retrieved and constructed on first use.

Example 1: Global A's constructor uses Global log

class AType
{    AType()  { log.report("A Constructed");}};

LogType    log;
AType      A;

// Or 
Class AType() 
{    AType()  { getLog().report("A Constructed");}};
LogType& getLog()
{
    static LogType  log;
    return log;
}
// Define A anywhere;

Example Global B's destructor uses Global log

Here you have to grantee that the object log is not destroyed before the object B. This means that log must be fully constructed before B (as the reverse order of destruction rule will then apply). Again the same techniques can be used. Either put them in the same translation unit or use a function to get log.

class BType
{    ~BType()  { log.report("B Destroyed");}};

LogType    log;
BType      B;   // B constructed after log (so B will be destroyed first)

// Or 
Class BType() 
{    BType()    { getLog();}
     /*
      * If log is used in the destructor then it must not be destroyed before B
      * This means it must be constructed before B 
      * (reverse order destruction guarantees that it will then be destroyed after B)
      *
      * To achieve this just call the getLog() function in the constructor.
      * This means that 'log' will be fully constructed before this object.
      * This means it will be destroyed after and thus safe to use in the destructor.
      */
    ~BType()    { getLog().report("B Destroyed");}
};
LogType& getLog()
{
    static LogType  log;
    return log;
}
// Define B anywhere;


回答5:

According to the C++ standard they are called before any function or object of their translation unit is used. Note that for objects in the global namespace this would mean they are initialized before main() is called. (See ltcmelo's and Martin's answers for mote details and a discussion of this.)