2016-03-07 3 views
1

Я пытаюсь перенести некоторый код на C++, который я изначально написал на Mac llvm для Windows Cygwin gcc. В этом проекте, я статический связывание еха с двумя библиотеками (я использую CMake):Перекрестные зависимости библиотеки C++ - портирование с llvm на gcc

add_executable(myexe main.cc) 
target_link_libraries(myexe lib1 lib2) 

В lib1 есть класс, который объявляет виртуальный метод:

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 не вызывается из lib1, так что это отлично работает.

Class1::method2 определяется в 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(); 
} 

Все это прекрасно работает, когда я компилирую и связать его с LLVM под MacOS. Когда я пытаюсь построить это с помощью gcc на Windows/Cygwin, я сталкиваюсь с различными ошибками компоновщика, например undefined reference to vtable или undefined reference to 'Class1:method2'. Фактическая ошибка зависит от упорядочения libs в вызове target_link_libraries.

Есть ли какие-либо параметры командной строки, которые я мог бы передать gcc/cmake, чтобы заставить это работать? Или, может быть, лучше рассмотреть другую инструментальную цепочку в Windows? Сейчас я использую IntelliJ CLion на обеих платформах.

Заранее за вашу помощь.

+0

Ваш пример отлично работает на моей установке cygwin, либо ваш файл cmake неверен, либо ваш пример не соответствует вашему примеру. Пожалуйста, проверьте пример, который вы опубликовали на вашем компьютере. также разместите свой файл cmake. упоминание вашей версии g ++ может помочь. – tejas

+0

Я не знаю, в какой компиляционной единице выделяется vtbl, но это может быть разным между clang и gcc. Возможно, gcc создает ситуацию, когда обе библиотеки зависят друг от друга. Связывание (по крайней мере, с GNU ld) однопроходное слева направо, поэтому вам нужно поставить зависимые библиотеки до предоставления, и вам, возможно, придется указывать libs несколько раз, например '-llib1 -llib2 -llib1', если ссылка lib2 представляет новая зависимость от lib1. –

+0

@ roland-w, большое спасибо за ваш комментарий. Вы предлагаете указывать libs несколько раз в 'target_link_libraries()' в cmake, действительно решила проблему. Если вы передадите свой комментарий в ответ, я буду отмечать его как правильно. –

ответ

1

Компилятор, вероятно, испускает VTBL для Class1, который содержит ссылку на Class1::method2, чтобы LIB1. Если один блок компиляции (то есть объектный файл) из lib2 определяет method1, а другой относится к Class1, библиотеки становятся взаимозависимыми. Поскольку компоновщик (по крайней мере, GNU ld) работает в однопроходном режиме по умолчанию, нужно дважды указывать lib2, один раз до и один раз после lib1. Поэтому директива cmake target_link_libraries(myexe lib2 lib1 lib2).

GNU ld также может разрешать все зависимости между наборами библиотек, прилагая их к --start-group и --end-group при существенном снижении производительности связи. Вы можете передать их через gcc через -Wl,--start-group и т. Д. Я не знаю, как получить cmake, чтобы сделать это.

Причина этого заключается в том, что библиотеки не связаны как целое, а на основе единицы компиляции, так что в исполняемом файле заканчиваются только те части библиотеки, которые на самом деле нужны. В данном случае ссылка на Class2 из основной программы вызывает неразрешенную ссылку на Class1 vtbl при связывании lib2. Связывание lib1 удовлетворяет этой ссылке, но создает еще один неразрешенный вопрос: Class1::method2, потому что адрес этого метода является частью vtbl. Таким образом, компоновщик должен пересмотреть lib2, чтобы решить эту проблему.

Обратите внимание, что эта проблема возникает только в том случае, если блок компиляции в lib2, ссылающийся на vtbl, не является тем, который определяет Class1::method2; в этом случае определение символа уже присутствует, и никакой второй проход по lib2 не требуется. Возможно, поэтому комментарий к вашему вопросу гласит, что ваш пример отлично работает.

Линкера работает в однопроходном режиме, исследуя библиотеки слева направо, потому что этот тип взаимозависимости встречается редко, и полное разрешение затрат на производительность (возможно, это также по умолчанию по историческим причинам, когда основная память была недостаточной и стеки перфокарт случайно не попадались случайно).

0

Ваше определение Class1 в lib1 является неполным - вы определяете его как виртуальный метод, а не чистый виртуальный метод.

Виртуальный метод должен иметь реализацию, если вы пытаетесь связать lib1, иначе связь не удастся - компоновщик не сможет найти какую-либо реализацию void Class1::method2(), потому что не существует. Я не уверен, как это делают другие компиляторы; возможно, это ошибка в других компиляторах, или ваша среда сборки не совсем так, как вы говорите.

Если вы отметили метод как быть чисто виртуальным, это позволит улучшить ситуацию, так как это позволит lib1 скомпилировать (хотя есть еще больше проблем):

class Class1 
{ 
public: 
    void method1(); 
    virtual void method2() = 0; // mark the method as pure virtual. 
} 

Есть несколько больше проблем, хотя - вы пытаетесь определить методы Class1 в другом файле, кроме Class1.cpp, а затем попытаться включить Class2 для компиляции и вызова этого метода в Class1.

Действительно похоже, что вы пытаетесь использовать наследование - вы хотите, чтобы Class1 :: method2() был чистым виртуальным, Class2 наследовал от Class1 и имел класс2, обеспечивающий реализацию метода method2().

Class1.ч:

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 
} 

И тогда, вместо того, чтобы вызывать Class2: : call_c1(), вы вызываете метод method2() напрямую:

SomeFile.cpp:

#include "Class2.h" 

int main() { 
    Class2 someInstance; 

    someInstance.method2(); 
} 
+0

Не имеет значения, в какой компиляционной единице фактически определена функция-член. Во всяком случае, символы разрешаются только во время ссылки. –

+0

@RolandW - Его исходное сообщение, похоже, указывает на то, что это проблема времени ссылки: «Действительная ошибка зависит от упорядочения libs в вызове target_link_libraries». Я согласен, это не имело бы значения для единицы компиляции, но это, безусловно, имеет значение для связи. При этом я думаю, что может возникнуть большая проблема с подходом аскеза. – antiduh

+0

@antiduh - неверные ваши замечания о неполном классе Class1. См. Комментарии к исходному сообщению, пример компиляции и ссылки в порядке. Я не собирался использовать наследование или чистые виртуальные методы здесь. Я согласен с тем, что код немного уродлив, скорее всего, не пройдет обзор в какой-либо авторитетной компании, но это разные дебаты. Фактически, я поднял его из упражнения в онлайн-курс (хотя это был не курс C++). Спасибо, что потрудились опубликовать ответ. –