tags:

views:

87

answers:

2

Hi all,

Please help me understand the root cause of the following behaviour.

In file a.cpp I have:

namespace NS {
   struct Obj {
      void pong(){ cout << "X in "__FILE__ << endl; }
      double k;
   };
   X::X() { Obj obj; obj.pong(); }
   void X::operator()() { cout << "X says hello" << endl; }
}

In file b.cpp I have:

namespace NS {
   struct Obj {
      void pong(){ cout << "Y in "__FILE__ << endl; }
      bool m;
   };
   Y::Y() { Obj obj; obj.pong(); }
   void Y::operator()() { cout << "Y says hello" << endl; }
}

My main creates an X, an Y and calls their operator()s:

int main( int argc, char *argv[] )
{
   NS::X x;
   x();

   NS::Y y;
   y();

   return 0;
}

The output of this program depends on whether a.cpp or b.cpp gets compiled first: in the first case the Obj from a.cpp is instantiated also within NS::Y's constructor, in the second case the Obj from b.cpp is instantiated in both NS::X and NS::Y.

% g++ b.cpp a.cpp main.cpp
% ./a.out

X in a.cpp
X says hello
Y in b.cpp
Y says hello

% g++ b.cpp a.cpp main.cpp
% ./a.out

Y in b.cpp
X says hello
Y in b.cpp
Y says hello

No warnings from the linker either on Linux or Visual Studio (2005). If I define Obj::pong() outside the declaration of the struct I get a linker error telling me that the Obj::pong function is multiply defined.

I experimented a bit further and found out that the cause must be related to whether or not the inlining, because if I compile with -O3, the each object uses the Obj from his own translation unit.

So then the question changes to: what happens to the second definition of the inline function during non-optimized compilation? Are they silently ignored?

+3  A: 

This is undefined behavior: The your class definitions define the same class type, and so they have to be both the same. For the linker it means it can choose one arbitrary definition as the one that gets emitted.

If you want them to be separated types, you have to nest them into an unnamed namespace. This will cause anything in that namespace to be unique for that translation unit:

namespace NS {
   namespace {
   struct Obj {
      void pong(){ cout << "Y in "__FILE__ << endl; }
      bool m;
   };
   }
   Y::Y() { Obj obj; obj.pong(); }
   void Y::operator()() { cout << "Y says hello" << endl; }
}

So then the question changes to: what happens to the second definition of the inline function during non-optimized compilation? Are they silently ignored?

Yes, for inline functions (functions defined within class definitions are inline, even if not explicitly declared inline), the same principle applies: They can be defined multiple times in the program, and the program behaves as if it was defined only once. To the linker it means again it can discard all but one definition. Which one it chooses is unspecified.

Johannes Schaub - litb
Thanks, I knew how to fix the situation, but I'm interested in what the compiler does in such a situation.
andreas buykx
@andreas buykx, the compiler does discard all but one definition. Not sure what can be further said. What are you interested in particular?
Johannes Schaub - litb
It can't be the compiler that makes this decision, because the compiler works with each cpp file separately. The linker decides which implementation to use, apparently it just picks the first.
andreas buykx
For GCC and ELF, inline functions are weak symbols ( http://www.tortall.net/projects/yasm/manual/html/objfmt-elf-directives.html#objfmt-elf-dir-weak ) ( http://en.wikipedia.org/wiki/Weak_symbol ). For instance you should find that if you make the function inline in one class definition, but not inline for the other one, then the non-inline version should be taken if you compile without optimizations. (since the linker chooses the global, non-weak symbol over the weak one). But this is not a description about how an implementation behaves: It's about a *particular* implementation.
Johannes Schaub - litb
A: 

Linker deals with mangled names. Please have a look here: http://en.wikipedia.org/wiki/Name%5Fmangling

So, as Johannes said, the behaviour is undefined, but details may be clarified:
If pong() is defined outside of the namespace, its name becomes unique and the linker complains correctly.

But if the name is hidden into the namespace, which is overlapped with the same one from another translation unit - as you have figured out - the linker does not complain. It just uses one symbol instead.
That's it.

I think, it is not specified and is implementation-specific for any compiler/linker.

avp
The namespace is actually not significant here: If I remove the namespaces, everything behaves as described.
andreas buykx