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