views:

1063

answers:

5

Hi,

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.

+9  A: 

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.

ebo
I have run into the static initialization problem before - a horrible situation to debug! I've updated my question.
Matthew Iselin
Only C++ Lite FAQ calls it a fiasco. It is not really that big of a problem when you know it exists. C++ Lite FAQ is riddled with 'Marshall' personal opinions not all of which are good.
Martin York
+4  A: 

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

sbi
This is true in many cases, but not guaranteed by the standard. Check the quote from the standard in my answer.
ltcmelo
@ltcmelo: Thanks, I didn't know that. I'll modify my answer accordingly.
sbi
ltcmelo is misquoting the standard. In the global namespace they ARE constructed before main(). In OTHER namespaces they MAY be lazily initialized.
Martin York
I was of course referring to your old answer, before you completly changed it ("All you know is they are called before main()" or similar), you may still remember.Deleted my comment, since it doesn't fit anymore.
drhirsch
@drhirsch: I knew you were referring to the old version. Still I don't get it. `:(`
sbi
@Martin and ltcmelo: I have again adapted my answer to what I consider an outcome of your debate. `:)`
sbi
+7  A: 

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 initilized first.

drhirsch
+3  A: 

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
ltcmelo
Its not a fiasco. It just something you need to be aware of. Stop quoteing the C++ Lite FAQ like that.
Martin York
You are misquoting the standard. In global namespace they ARE constructed before main. In other namespace's they MAY be lazily initialized.
Martin York
I quoted the standard, not the C++ FAQ. I just used the "expression" already used in this discussion (which happens to be from the FAQ).
ltcmelo
The standard does refer to namespace scope and global scope. And it makes clear that global scope is a short for global namespace scope. Your statement regarding the initialization before main then makes me a bit confused. Would you please clarify this to me by pointing where the standard provides that guarantee for global scoped objects? It's not that I doubt you, it's just that I would like to know where and how this is put.
ltcmelo
I don't have the standard on me (l'll check tonight for the exact reference). But even following your logic all static non-local variables in the global namespace will be constructed before main(). main() is in the global namespace? Your snipet gurantees that all non-local staics in a namespace are constructed before any function in that namespace is called. Thus guranteeing that all static non-local variables in the global namespace are constructed before main() is called! This also makes it backward compatable with C which has the same behavior.
Martin York
+2  A: 

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;
Martin York
He asks specifically about gcc. There is a way to gurantee the order of initialization between translation untis, see my answer for details.
drhirsch
@drhirsch: I know. But why use a compiler specific hack when the language supports such an easy solution to the probelm.
Martin York
@Marting York: Well, easy seems rather subjective ;-) For me __attribute__((init_priority(PRIORITY))) seems way easier, and I don't consider it an ugly hack. But thats probably personal taste.But don't forget, some of the gcc developers actually write the standards, so there is a slight chance, one of those extensions may come up as a language feature one day ;-)
drhirsch
hack: => Generic code that is relies on a feature that is Hardware/OS/Compiler specific.
Martin York