We have two source files, a.cpp and b.cpp and a header file named constructions.h.
We define a simple C++ class with
the same name (class M, for instance) in each source file, respectively.
The file a.cpp looks like this:
#include "iostream"
#include "constructions.h"
class M
{
int i;
public:
M(): i( -1 ) { cout << "M() from a.cpp" << endl; }
M( int a ) : i( a ) { cout << "M(int) from a.cpp, i: " << i << endl; }
M( const M& b ) { i = b.i; cout << "M(M&) from a.cpp, i: " << i << endl; }
M&
operator = ( M& b )
{
i = b.i;
cout << "M::operator =(), i: " << i << endl;
return *this;
}
virtual ~M(){ cout << "M::~M() from a.cpp" << endl; }
operator int() { cout << "M::operator int() from a.cpp" << endl; return i; }
};
void test1()
{
cout << endl << "Example 1" << endl;
M b1;
cout << "b1: " << b1 << endl;
cout << endl << "Example 2" << endl;
M b2 = 5;
cout << "b2: " << b2 << endl;
cout << endl << "Example 3" << endl;
M b3(6);
cout << "b3: " << b3 << endl;
cout << endl << "Example 4" << endl;
M b4 = b1;
cout << "b4: " << b4 << endl;
cout << endl << "Example 5" << endl;
M b5; b5 = b2;
cout << "b5: " << b5 << endl;
}
int main(int argc, char* argv[])
{
test1();
test2();
cin.get();
return 0;
}
The file b.cpp looks like this:
#include "iostream"
#include "constructions.h"
class M
{
public:
M() { cout << "M() from b.cpp" << endl; }
~M() { cout << "M::~M() from b.cpp" << endl; }
};
void test2()
{
M m;
}
Finally,
the file constructions.h contains only
the declaration of
the function "test2()" (which is defined in "b.cpp"), so that it can be used in "a.cpp":
using namespace std;
void test2();
We compiled and linked these three files using either VS2005 or
the GNU 4.1.0 compiler and
the 2.16.91 ld linker under Suse.
The results are surprising and different between
the two build environments. But in both cases it looks like
the linker gets confused about which definition of
the class M it should use.
If we comment out
the definition of test2() from b.cpp and its invocation from a.cpp, then all
the C++ objects created in test1() are of
the type M defined in a.cpp and
the program executes normally under Windows and Suse. Here is
the run output under Windows:
Example 1
M() from a.cpp
M::operator int() from a.cpp
b1: -1
Example 2
M(int) from a.cpp, i: 5
M::operator int() from a.cpp
b2: 5
Example 3
M(int) from a.cpp, i: 6
M::operator int() from a.cpp
b3: 6
Example 4
M(M&) from a.cpp, i: -1
M::operator int() from a.cpp
b4: -1
Example 5
M() from a.cpp
M::operator =(), i: 5
M::operator int() from a.cpp
b5: 5
M::~M() from a.cpp
M::~M() from a.cpp
M::~M() from a.cpp
M::~M() from a.cpp
M::~M() from a.cpp
If we enable
the definition of test2() in "b.cpp" but comment out its invocation from main(), then
the results are different.
Under Suse,
the C++ objects created in test1() are still of
the type M defined in a.cpp and
the program still seems to execute normally.
The VS2005 versions behave differently in Debug or Release mode: in Debug mode,
the program still seems to execute normally, but in Release mode, b1 and b5 are of
the type M defined in b.cpp (as
the constructor invocation proves), although
the other member functions called (including
the destructor), belong to M defined in a.cpp. Here is
the run output for
the executable built in Release mode:
Example 1
M() from b.cpp
M::operator int() from a.cpp
b1: 4206872
Example 2
M(int) from a.cpp, i: 5
M::operator int() from a.cpp
b2: 5
Example 3
M(int) from a.cpp, i: 6
M::operator int() from a.cpp
b3: 6
Example 4
M(M&) from a.cpp, i: 4206872
M::operator int() from a.cpp
b4: 4206872
Example 5
M() from b.cpp
M::operator =(), i: 5
M::operator int() from a.cpp
b5: 5
M::~M() from a.cpp
M::~M() from a.cpp
M::~M() from a.cpp
M::~M() from a.cpp
M::~M() from a.cpp
Finally, if we allow
the call to test2() from main,
the program misbehaves in all circumstances (that is under Suse and under Windows in both Debug and Release modes).
The Windows-Debug version finds a memory corruption around
the variable m, defined in test2(). Here is
the Windows output in Release mode (test2() seems to have created an instance of M defined in b.cpp):
Example 1
M() from b.cpp
M::operator int() from a.cpp
b1: 4206872
Example 2
M(int) from a.cpp, i: 5
M::operator int() from a.cpp
b2: 5
Example 3
M(int) from a.cpp, i: 6
M::operator int() from a.cpp
b3: 6
Example 4
M(M&) from a.cpp, i: 4206872
M::operator int() from a.cpp
b4: 4206872
Example 5
M() from b.cpp
M::operator =(), i: 5
M::operator int() from a.cpp
b5: 5
M::~M() from a.cpp
M::~M() from a.cpp
M::~M() from a.cpp
M::~M() from a.cpp
M::~M() from a.cpp
M() from b.cpp
M::~M() from b.cpp
And here is
the Suse output.
The objects created in test1() are of
the type M defined in a.cpp but
the object created in test2() is also of
the type M defined in a.cpp, unlike
the object created under Windows which is of
the type M defined in b.cpp.
The program crashed in
the end:
Example 1
M() from a.cpp
M::operator int() from a.cpp
b1: -1
Example 2
M(int) from a.cpp, i: 5
M::operator int() from a.cpp
b2: 5
Example 3
M(int) from a.cpp, i: 6
M::operator int() from a.cpp
b3: 6
Example 4
M(M&) from a.cpp, i: -1
M::operator int() from a.cpp
b4: -1
Example 5
M() from a.cpp
M::operator =(), i: 5
M::operator int() from a.cpp
b5: 5
M::~M() from a.cpp
M::~M() from a.cpp
M::~M() from a.cpp
M::~M() from a.cpp
M::~M() from a.cpp
M() from a.cpp
M::~M() from a.cpp
Segmentation fault (core dumped)
I couldn't make
the angle brackets appear using Markdown, so I used quotes around
the header file name iostream. Otherwise,
the code could be copied verbatim and tried. It is purely scholastic.
The statement cin.get() at
the end of main() was included just to facilitate running
the program directly from VS2005 (cause it to display
the output window until we could analyze
the output).
We are looking for a software engineer in Sunnyvale, CA and may offer that position to
the programmer capable of providing an intelligent and comprehensive explanation of these anomalies.
I can be contacted at
[email protected].