Reputation: 1194
I am trying to port some C++ code that I have originally written on Mac llvm to Windows Cygwin gcc. In this project, I am statically linking an exe with two libraries (I am using cmake):
add_executable(myexe main.cc)
target_link_libraries(myexe lib1 lib2)
In lib1
there is a class, that declares a virtual method:
lib1/Class1.h:
class Class1
{
public:
void method1();
virtual void method2();
};
lib1/Class1.cpp:
#include "Class1.h"
void Class1::method1() {
// do work
}
// Note that method2 is not defined!
Class1::method2
is not called from lib1
, so this works fine.
Class1::method2
is defined in lib2
:
lib2/Class2.h:
#include "Class1.h"
class Class2
{
private:
Class1 c1;
public:
void call_c1();
};
in lib2/Class2.cpp:
#include "Class2.h"
void Class1::method2() {
// do some other work
}
void Class2::call_c1() {
c1.method2();
}
All of this works just fine when I compile and link it with llvm under MacOS. When I try to build this with gcc on Windows/Cygwin, I run into all sorts of linker errors, like undefined reference to vtable
or undefined reference to 'Class1:method2'
. The actual error depends on the ordering of libs in target_link_libraries
call.
Are there any command line options that I could pass to gcc/cmake to get this to work? Or maybe it is better to consider another toolchain on Windows? I am actually using IntelliJ CLion on both platforms now.
Thanks in advance for your help.
Upvotes: 1
Views: 281
Reputation: 1461
The compiler probably emits the vtbl for Class1
, which contains a reference to Class1::method2
, to lib1. If one compilation unit (i.e. object file) from lib2 defines method1
and another refers to Class1
the libraries become interdependent. Since the linker (at least GNU ld) works in one-pass mode by default one has to specify lib2 twice, once before and once after lib1. The cmake directive is therefore target_link_libraries(myexe lib2 lib1 lib2)
.
GNU ld can also resolve all dependencies among a set of libraries by enclosing them with --start-group
and --end-group
— at a significant linking performance penalty. You can pass them through gcc via -Wl,--start-group
, etc. I don't know how to get cmake to do that, though.
The reason behind this is that libraries are not linked as a whole but on a compilation unit basis, so that only the parts of a library that are actually needed end up in the executable. In the present case referencing Class2
from the main program causes an unresolved reference to the Class1
vtbl when linking lib2. Linking lib1 satisfies this reference, but creates another unresolved one to Class1::method2
because the address of this method is part of the vtbl. So the linker has to reexamine lib2 to resolve that.
Note that this problem only appears if the compilation unit in lib2 referencing the vtbl is not the one that defines Class1::method2
; in that case the symbol definition is already present and no second pass over lib2 is necessary. Maybe that is why a comment to your question states that your example works fine.
The linker works in one-pass mode, examining libraries from left to right, because this type of interdependency is rare and the full resolution costs performance (it is probably also the default for historical reasons when core memory was scarce and stacks of punch cards were not easily accessed randomly).
Upvotes: 2
Reputation: 12425
Your definition of Class1
in lib1 is incomplete - you define it to be a virtual method, and not a pure virtual method.
A virtual method must have an implementation provided when you attempt to link lib1, else linking will fail - the linker won't be able to find any implementation of void Class1::method2()
because none exists. I'm not sure how other compilers are allowing this; perhaps it is a bug in the other compilers, or your build environment is not exactly as you say it is.
If you marked the method as being pure virtual, this would improve the situation since it will allow lib1 to compile (though there are still more problems):
class Class1
{
public:
void method1();
virtual void method2() = 0; // mark the method as pure virtual.
}
There are a few bigger problems though - you're trying to define Class1's methods in a different file than Class1.cpp, and then trying to enable Class2 to compile and invoke this method in Class1.
It really looks like you're trying to use inheritance - you want Class1::method2() to be pure virtual, Class2 to inherit from Class1, and have Class2 provide an implementation for method2().
Class1.h:
class Class1
{
public:
void method1();
virtual void method2() = 0;
};
Class1.cpp:
#include "Class1.h"
void Class1::method1() {
// do work
}
Class2.h:
#include "Class1.h"
class Class2 : public Class1
{
public:
virtual void method2();
};
Class2.cpp:
#include "Class2.h"
// - Pay close attention to this small but important change.
// V
void Class2::method2() {
// do some other work
}
And then, instead of invoking Class2::call_c1(), you would invoke method2() directly:
SomeFile.cpp:
#include "Class2.h"
int main() {
Class2 someInstance;
someInstance.method2();
}
Upvotes: 0