Evgeny Tanhilevich
Evgeny Tanhilevich

Reputation: 1194

C++ library cross dependencies - porting from llvm to gcc

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

Answers (2)

Roland W
Roland W

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

antiduh
antiduh

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

Related Questions