0

I have a code:

a.h

...
namespace X
{
    const std::string Foo = "foo";
    inline std::string getFoo()
    {
        return Foo;
    }
}
...

a.cpp:

#include "a.h"
...
namespace X
{
   const string Default_Foo = getFoo();
}
...

Of course there are more files in a project that include a.h

The program results a segfault in start. Investigation showed that:

  1. Several copies of Foo are created in the program
    bash-4.2# nm -oC a.out | grep Foo
    a.out:3c162c50 b X::Foo
    a.out:3c162990 b X::Foo
    a.out:3c1641b0 b X::Foo

2.During initialization Default_Foo calls getFoo(), which does not take the already initialized Foo from a.cpp compilation unit, but instead it takes Foo from another compilation unit, which is by chance, is not initialized yet. This clearly results in a segfault.

Can someone give me a reasoning to such a behavior? What is the best defense strategy against such problems in future?

Finally what I am the most curious about is why getFoo() uses Foo from another compilation unit.

Vitaly P
  • 1,121
  • 3
  • 11
  • 21
  • There are no static variables in your code anywhere - what makes you think this is the issue? Please try to post a complete, minimal example that exposes the error. – Björn Pollex Dec 11 '13 at 10:56
  • 3
    @BjörnPollex While there are no variables declared `static`, there are variables with static storage duration. I'm guessing this is what he is referring to. – Agentlien Dec 11 '13 at 11:00
  • @Agentlien: Right - I was not aware of that rule. – Björn Pollex Dec 11 '13 at 11:03

3 Answers3

3

Your program is violating the one definition rule (ODR).

Inline functions may be defined in more than one compilation unit, but it is required that they must be defined consistently, that is, every definition be composed of the same sequence of lexemes, and every used identifier within that sequence must denote the same object.

In your case, the identifier Foo denotes different objects across compilation units (namespace-level object with the modifier const have internal linkage by default, thus every compilation unit has its own X::Foo).

The tools you use to build your program might, but are not required to, diagnose violations of this. In your case they didn't. The linker just picked the first definition of X::getFoo it came across without much deliberating over it.

ach
  • 2,314
  • 1
  • 13
  • 23
2

Several copies of X::Foo are created because you declare and define X::Foo in header. As a result you get as many declaration and definitions of X::Foo as you include it into sources. Actually, it usually result in error at link time due to multiple definitions, but you avoided that by having X::Foo internal linkage. (see legends2k comment for explanation)

Static initialization itself has an order only within unit of compilation. It is indeed a problem to get proper static initialization order between multiple compilation objects. There are compiler-specific extension to achieve that, but note that they have severe limitation.

The way code provided I see no reason why X::getFoo() call wouldn't be inlined, thus eliminating any chance for dynamic symbol resolution. X::Foo itself should not be messed by name either. You should check symbolic names/relocation tables for resulting binary to see if X::getFoo() or X::Foo is dynamically resolved.

One of solutions to avoid the problem is to avoid global static variables. Instead, use this approach:

static const std::string& getFoo()
{
    static const std::string val = "abc";
    return val;
}

You are given promise that:

  • val will be initialized at first call to getFoo()
  • it is thread-safe
nyrl
  • 769
  • 5
  • 15
0

After discussing this problem in another forums, I'm inclined to think that the root of the problems sits in a fact, that compiler creates multiple copies of getFoo() per translation unit each referring to Foo from this unit. During linking the linker thinks that all getFoo() are equivalent and picks up the first, which is not guaranteed to be the one from a.cpp translation unit.

Any comments are welcome.

Vitaly P
  • 1,121
  • 3
  • 11
  • 21
  • It is exactly what would happens if there were no `inline` keyword before `X::getFoo()`. Of course, it might be the case when `inline` hint is ignored, e.g. due to link options; otherwise method looks pretty small to be rejected for inlining. – nyrl Dec 11 '13 at 12:36
  • There are no extra compile or link options given. – Vitaly P Dec 11 '13 at 17:25
  • Apart from the fact that your code is invalid (see Andrey's answer), you can check what is happening exactly. As for me, the main question is whether `X::getFoo()` call gets inlined. Inline is a hint, so it may be ignored. You can check if call is actually inlined by looking at assembly code. If call is not inlined, then you got proper explanation of what is going on at *another forum*. If call is inlined, then... it is completely strange. – nyrl Dec 11 '13 at 18:43