Consider the following example:
// usedclass1.hpp
#include <iostream>
class UsedClass
{
public:
UsedClass() { }
void doit() { std::cout << "UsedClass 1 (" << this << ") doit hit" << std::endl; }
};
// usedclass2.hpp
#include <iostream>
class UsedClass
{
public:
UsedClass() { }
void doit() { std::cout << "UsedClass 2 (" << this << ") doit hit" << std::endl; }
};
// object.hpp
class Object
{
public:
Object();
};
// object.cpp
#include "object.hpp"
#include "usedclass2.hpp"
Object::Object()
{
UsedClass b;
b.doit();
}
// main.cpp
#include "usedclass1.hpp"
#include "object.hpp"
int main()
{
Object obj;
UsedClass a;
a.doit();
}
The code compiles without any compiler or linker errors. But the output is strange for me:
gcc (Red Hat 4.6.1-9) on Fedora x86_64 with no optimization [EG1]:
UsedClass 1 (0x7fff0be4a6ff) doit hit
UsedClass 1 (0x7fff0be4a72e) doit hit
same as [EG1] but with -O2 option enabled [EG2]:
UsedClass 2 (0x7fffcef79fcf) doit hit
UsedClass 1 (0x7fffcef79fff) doit hit
msvc2005 (14.00.50727.762) on Windows XP 32bit with no optimization [EG3]:
UsedClass 1 (0012FF5B) doit hit
UsedClass 1 (0012FF67) doit hit
same as [EG3] but with /O2 (or /Ox) enabled [EG4]:
UsedClass 1 (0012FF73) doit hit
UsedClass 1 (0012FF7F) doit hit
I would expect either a linker error (assuming ODR rule is violated) or the output as in [EG2] (code is inlined, nothing is exported from the translation unit, ODR rule is held). Thus my questions:
Thank you for any suggestions, comments and standard interpretations.
Update
I would like to understand the compiler's behaviour. More precisely, why there are no errors generated if the ODR is violated. A hypothesis is that since all functions in classes UsedClass1 and UsedClass2 are marked as inline (and therefore C++03 3.2 is not violated) the linker doesn't report errors, but in this case outputs [EG1], [EG3], [EG4] seem strange.
This is the rule that prohibits what you're doing (the C++11 wording), from section 3.2 of the Standard:
There can be more than one definition of a class type (Clause 9), enumeration type (7.2), inline function with external linkage (7.1.2), class template (Clause 14), non-static function template (14.5.6), static data member of a class template (14.5.1.3), member function of a class template (14.5.1.1), or template specialization for which some template parameters are not specified (14.7, 14.5.5) in a program provided that each definition appears in a different translation unit, and provided the definitions satisfy the following requirements. Given such an entity named
D
defined in more than one translation unit, then
each definition of
D
shall consist of the same sequence of tokens; andin each definition of
D
, corresponding names, looked up according to 3.4, shall refer to an entity defined within the definition ofD
, or shall refer to the same entity, after overload resolution (13.3) and after matching of partial template specialization (14.8.3), except that a name can refer to aconst
object with internal or no linkage if the object has the same literal type in all definitions ofD
, and the object is initialized with a constant expression (5.19), and the value (but not the address) of the object is used, and the object has the same value in all definitions ofD
; andin each definition of
D
, corresponding entities shall have the same language linkage; andin each definition of
D
, the overloaded operators referred to, the implicit calls to conversion functions, constructors, operator new functions and operator delete functions, shall refer to the same function, or to a function defined within the definition of D; andin each definition of
D
, a default argument used by an (implicit or explicit) function call is treated as if its token sequence were present in the definition ofD
; that is, the default argument is subject to the three requirements described above (and, if the default argument has sub-expressions with default arguments, this requirement applies recursively).if
D
is a class with an implicitly-declared constructor (12.1), it is as if the constructor was implicitly defined in every translation unit where it is odr-used, and the implicit definition in every translation unit shall call the same constructor for a base class or a class member ofD
.
In your program, you're violating the ODR for class UsedClass
because the tokens are different in different compilation units. You could fix that by moving the definition of UsedClass::doit()
outside the class body, but the same rule applies to the body of inline functions.
Your program violates the One Definition Rule and invokes an Undefined Behavior.
The standard does not mandate an diagnostic message if you break the ODR but the behavior is Undefined.
C++03 3.2 One definition rule
No translation unit shall contain more than one definition of any variable, function, class type, enumeration type or template. ...
Every program shall contain exactly one definition of every non-inline function or object that is used in that program; no diagnostic required. The definition can appear explicitly in the program, it can be found in the standard or a user-defined library, or (when appropriate) it is implicitly defined (see 12.1, 12.4 and 12.8). An inline function shall be defined in every translation unit in which it is used.
Further the standard defines specific requirements for existence of multiple definitions of an symbol, those are aptly defined in Para #5 of 3.2.
There can be more than one definition of a class type (clause 9), enumeration type (7.2), inline function with external linkage (7.1.2), class template (clause 14), non-static function template (14.5.5), static data member of a class template (14.5.1.3), member function of a class template (14.5.1.1), or template specialization for which some template parameters are not specified (14.7, 14.5.4) in a program provided that each definition appears in a different translation unit, and provided the definitions satisfy the following requirements. Given such an entity named D defined in more than one translation unit, then
— each definition of D shall consist of the same sequence of tokens; and ...
Why are outputs [EG1], [EG3], [EG4] possible?
The simple answer is that the behaviour is undefined, so anything is possible.
Most compilers handle an inline function by generating a copy in each translation unit in which it's defined; the linker then arbitrarily chooses one to include in the final program. This is why, with optimisations disabled, it calls the same function in both cases. With optimisations enabled, the function might be inlined by the compiler, in which case each inlined call will use the version defined in the current translation unit.
That makes me think that the standard somehow doesn't specify the behaviour in this case.
That's correct. Breaking the one definition rule gives undefined behaviour, and no diagnostic is required.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With