Что такое неопределенные ссылки / неразрешенные внешние ошибки символов? Каковы общие причины и как их исправить / предотвратить?
Не стесняйтесь редактировать / добавлять свои собственные.
Что такое неопределенные ссылки / неразрешенные внешние ошибки символов? Каковы общие причины и как их исправить / предотвратить?
Не стесняйтесь редактировать / добавлять свои собственные.
Ответы:
Компиляция программы на C ++ происходит в несколько этапов, как указано в 2.2 (благодарность Кита Томпсону за справку) :
Приоритет среди синтаксических правил перевода определяется следующими этапами [см. Сноску] .
- Физические символы исходного файла отображаются, в соответствии с реализацией, в базовый исходный набор символов (ввод символов новой строки для индикаторов конца строки), если это необходимо. [СНиП]
- Каждый экземпляр символа обратной косой черты (\), за которым сразу следует символ новой строки, удаляется, объединяя физические исходные строки для формирования логических исходных строк. [СНиП]
- Исходный файл разбит на токены предварительной обработки (2.5) и последовательности символов пробела (включая комментарии). [СНиП]
- Выполняются директивы предварительной обработки, расширяются вызовы макросов и выполняются выражения унарного оператора _Pragma. [СНиП]
- Каждый элемент исходного набора символов в символьном литерале или строковом литерале, а также каждая escape-последовательность и универсальное имя-символа в символьном литерале или строковом литерале, не являющемся необработанным, преобразуются в соответствующий элемент набора исполняемых символов; [СНиП]
- Литеральные токены смежных строк объединяются.
- Пробельные символы, разделяющие токены, больше не имеют значения. Каждый токен предварительной обработки преобразуется в токен. (2.7). Полученные токены синтаксически и семантически анализируются и переводятся как единица перевода. [СНиП]
- Переведенные единицы перевода и единицы реализации объединяются следующим образом: [SNIP]
- Все ссылки на внешние объекты разрешены. Компоненты библиотеки связаны для удовлетворения внешних ссылок на объекты, не определенные в текущем переводе. Весь такой вывод транслятора собирается в образ программы, который содержит информацию, необходимую для выполнения в среде выполнения. (акцент мой)
[Примечание] Реализации должны вести себя так, как если бы эти отдельные фазы происходили, хотя на практике разные фазы могут складываться вместе.
Указанные ошибки возникают на этом последнем этапе компиляции, который чаще всего называют связыванием. По сути, это означает, что вы скомпилировали кучу файлов реализации в объектные файлы или библиотеки, и теперь вы хотите, чтобы они работали вместе.
Скажем, вы определили символ a
в a.cpp
. Теперь b.cpp
объявил этот символ и использовал его. Перед связыванием он просто предполагает, что этот символ был определен где-то , но ему все равно где. Фаза связывания отвечает за поиск символа и правильное связывание его b.cpp
(ну, собственно, с объектом или библиотекой, которая его использует).
Если вы используете Microsoft Visual Studio, вы увидите, что проекты генерируют .lib
файлы. Они содержат таблицу экспортируемых символов и таблицу импортированных символов. Импортированные символы сопоставляются с библиотеками, с которыми вы ссылаетесь, и экспортируемые символы предоставляются для библиотек, которые используют.lib
(если таковые имеются).
Подобные механизмы существуют для других компиляторов / платформ.
Общие сообщения об ошибках error LNK2001
, error LNK1120
, error LNK2019
для Microsoft Visual Studio и undefined reference to
SymbolName для GCC .
Код:
struct X
{
virtual void foo();
};
struct Y : X
{
void foo() {}
};
struct A
{
virtual ~A() = 0;
};
struct B: A
{
virtual ~B(){}
};
extern int x;
void foo();
int main()
{
x = 0;
foo();
Y y;
B b;
}
сгенерирует следующие ошибки с GCC :
/home/AbiSfw/ccvvuHoX.o: In function `main':
prog.cpp:(.text+0x10): undefined reference to `x'
prog.cpp:(.text+0x19): undefined reference to `foo()'
prog.cpp:(.text+0x2d): undefined reference to `A::~A()'
/home/AbiSfw/ccvvuHoX.o: In function `B::~B()':
prog.cpp:(.text._ZN1BD1Ev[B::~B()]+0xb): undefined reference to `A::~A()'
/home/AbiSfw/ccvvuHoX.o: In function `B::~B()':
prog.cpp:(.text._ZN1BD0Ev[B::~B()]+0x12): undefined reference to `A::~A()'
/home/AbiSfw/ccvvuHoX.o:(.rodata._ZTI1Y[typeinfo for Y]+0x8): undefined reference to `typeinfo for X'
/home/AbiSfw/ccvvuHoX.o:(.rodata._ZTI1B[typeinfo for B]+0x8): undefined reference to `typeinfo for A'
collect2: ld returned 1 exit status
и подобные ошибки с Microsoft Visual Studio :
1>test2.obj : error LNK2001: unresolved external symbol "void __cdecl foo(void)" (?foo@@YAXXZ)
1>test2.obj : error LNK2001: unresolved external symbol "int x" (?x@@3HA)
1>test2.obj : error LNK2001: unresolved external symbol "public: virtual __thiscall A::~A(void)" (??1A@@UAE@XZ)
1>test2.obj : error LNK2001: unresolved external symbol "public: virtual void __thiscall X::foo(void)" (?foo@X@@UAEXXZ)
1>...\test2.exe : fatal error LNK1120: 4 unresolved externals
Общие причины включают в себя:
#pragma
(Microsoft Visual Studio)UNICODE
определенияvirtual
деструктору нужна реализация.Чтобы объявить деструктор чистым, вы все равно должны его определить (в отличие от обычной функции):
struct X
{
virtual ~X() = 0;
};
struct Y : X
{
~Y() {}
};
int main()
{
Y y;
}
//X::~X(){} //uncomment this line for successful definition
Это происходит потому, что деструкторы базового класса вызываются, когда объект уничтожается неявно, поэтому требуется определение.
virtual
методы должны быть либо реализованы, либо определены как чистые.Это похоже наvirtual
методы без определения, с дополнительным обоснованием того, что чистое объявление генерирует фиктивную vtable, и вы можете получить ошибку компоновщика без использования функции:
struct X
{
virtual void foo();
};
struct Y : X
{
void foo() {}
};
int main()
{
Y y; //linker error although there was no call to X::foo
}
Чтобы это работало, объявляем X::foo()
чистым:
struct X
{
virtual void foo() = 0;
};
virtual
классаНекоторые члены должны быть определены, даже если они не используются явно:
struct A
{
~A();
};
Следующее приведет к ошибке:
A a; //destructor undefined
Реализация может быть встроенной в самом определении класса:
struct A
{
~A() {}
};
или снаружи:
A::~A() {}
Если реализация находится за пределами определения класса, но в заголовке, методы должны быть помечены как inline
предотвращающие множественное определение.
Все используемые методы-члены должны быть определены, если они используются.
struct A
{
void foo();
};
void foo() {}
int main()
{
A a;
a.foo();
}
Определение должно быть
void A::foo() {}
static
члены данных должны быть определены вне класса в одной единице перевода :struct X
{
static int x;
};
int main()
{
int x = X::x;
}
//int X::x; //uncomment this line to define X::x
Инициализатор может быть предоставлен для static
const
члена данных целочисленного или перечислимого типа в определении класса; однако использование odr этого члена все равно потребует определения области имен, как описано выше. C ++ 11 позволяет инициализацию внутри класса для всех static const
членов данных.
Обычно каждая единица перевода генерирует объектный файл, который содержит определения символов, определенных в этой единице перевода. Чтобы использовать эти символы, вы должны ссылаться на эти объектные файлы.
В gcc вы должны указать все объектные файлы, которые должны быть связаны вместе в командной строке, или скомпилировать файлы реализации вместе.
g++ -o test objectFile1.o objectFile2.o -lLibraryName
libraryName
Здесь просто голое имя библиотеки, без платформы конкретных дополнений. Так, например, в Linux файлы библиотеки обычно называются, libfoo.so
но вы только пишете -lfoo
. В Windows этот же файл может быть вызван foo.lib
, но вы будете использовать тот же аргумент. Возможно, вам придется добавить каталог, где эти файлы могут быть найдены с помощью -L‹directory›
. Убедитесь, что не пишете пробел после -l
или-L
.
Для XCode : добавьте пути поиска по заголовку пользователя -> добавьте путь поиска по библиотеке -> перетащите фактическую ссылку на библиотеку в папку проекта.
В MSVS файлы, добавленные в проект, автоматически связывают свои объектные файлы, и lib
файл будет создан (в обычном использовании). Чтобы использовать символы в отдельном проекте, вам необходимо включить lib
файлы в настройки проекта. Это делается в разделе Linker свойств проекта, в Input -> Additional Dependencies
. (путь к lib
файлу должен быть добавлен Linker -> General -> Additional Library Directories
) При использовании сторонней библиотеки, которая поставляется сlib
файлом, невозможность сделать это обычно приводит к ошибке.
Также может случиться, что вы забудете добавить файл в компиляцию, и в этом случае объектный файл не будет сгенерирован. В gcc вы добавляете файлы в командную строку. В MSVS добавлении файла в проект сделает его скомпилировать его автоматически (хотя файлы можно вручную, индивидуально исключены из сборки).
В программировании Windows контрольным признаком того, что вы не связали необходимую библиотеку, является то, что имя неразрешенного символа начинается с __imp_
. Посмотрите название функции в документации, и там должно быть указано, какую библиотеку вам нужно использовать. Например, MSDN помещает информацию в поле внизу каждой функции в разделе «Библиотека».
gcc main.c
вместо gcc main.c other.c
(что часто делают новички до того, как их проекты становятся такими большими, чтобы создавать файлы .o).
Типичное объявление переменной
extern int x;
Поскольку это только декларация, необходимо одно определение . Соответствующее определение будет:
int x;
Например, следующее может привести к ошибке:
extern int x;
int main()
{
x = 0;
}
//int x; // uncomment this line for successful definition
Аналогичные замечания относятся к функциям. Объявление функции без ее определения приводит к ошибке:
void foo(); // declaration only
int main()
{
foo();
}
//void foo() {} //uncomment this line for successful definition
Будьте осторожны, чтобы реализуемая вами функция точно соответствовала той, которую вы объявили. Например, вы могли не соответствовать cv-квалификаторам:
void foo(int& x);
int main()
{
int x;
foo(x);
}
void foo(const int& x) {} //different function, doesn't provide a definition
//for void foo(int& x)
Другие примеры несоответствий включают
Сообщение об ошибке от компилятора часто дает вам полное объявление переменной или функции, которая была объявлена, но никогда не определялась. Сравните это с приведенным вами определением. Убедитесь, что каждая деталь соответствует.
#includes
не добавленными в исходный каталог, также попадают в категорию отсутствующих определений.
Порядок, в котором связаны библиотеки, имеет значение, если библиотеки зависят друг от друга. Как правило, если библиотека A
зависит от библиотеки B
, она libA
ДОЛЖНА появляться раньшеlibB
в флагах компоновщика.
Например:
// B.h
#ifndef B_H
#define B_H
struct B {
B(int);
int x;
};
#endif
// B.cpp
#include "B.h"
B::B(int xx) : x(xx) {}
// A.h
#include "B.h"
struct A {
A(int x);
B b;
};
// A.cpp
#include "A.h"
A::A(int x) : b(x) {}
// main.cpp
#include "A.h"
int main() {
A a(5);
return 0;
};
Создайте библиотеки:
$ g++ -c A.cpp
$ g++ -c B.cpp
$ ar rvs libA.a A.o
ar: creating libA.a
a - A.o
$ ar rvs libB.a B.o
ar: creating libB.a
a - B.o
Обобщение:
$ g++ main.cpp -L. -lB -lA
./libA.a(A.o): In function `A::A(int)':
A.cpp:(.text+0x1c): undefined reference to `B::B(int)'
collect2: error: ld returned 1 exit status
$ g++ main.cpp -L. -lA -lB
$ ./a.out
Так что еще раз повторить, порядок ДЕЛАЕТ дело!
что такое «неопределенная ссылка / неразрешенный внешний символ»
Я попытаюсь объяснить, что такое «неопределенная ссылка / неразрешенный внешний символ».
примечание: я использую g ++ и Linux, и все примеры для него
Например, у нас есть код
// src1.cpp
void print();
static int local_var_name; // 'static' makes variable not visible for other modules
int global_var_name = 123;
int main()
{
print();
return 0;
}
а также
// src2.cpp
extern "C" int printf (const char*, ...);
extern int global_var_name;
//extern int local_var_name;
void print ()
{
// printf("%d%d\n", global_var_name, local_var_name);
printf("%d\n", global_var_name);
}
Сделать объектные файлы
$ g++ -c src1.cpp -o src1.o
$ g++ -c src2.cpp -o src2.o
После фазы ассемблера у нас есть объектный файл, который содержит любые символы для экспорта. Посмотрите на символы
$ readelf --symbols src1.o
Num: Value Size Type Bind Vis Ndx Name
5: 0000000000000000 4 OBJECT LOCAL DEFAULT 4 _ZL14local_var_name # [1]
9: 0000000000000000 4 OBJECT GLOBAL DEFAULT 3 global_var_name # [2]
Я отклонил некоторые строки из вывода, потому что они не имеют значения
Итак, мы видим следующие символы для экспорта.
[1] - this is our static (local) variable (important - Bind has a type "LOCAL")
[2] - this is our global variable
src2.cpp ничего не экспортирует, и мы не видели его символов
Связать наши объектные файлы
$ g++ src1.o src2.o -o prog
и запустить его
$ ./prog
123
Линкер видит экспортированные символы и связывает их. Теперь мы пытаемся раскомментировать строки в src2.cpp, как здесь
// src2.cpp
extern "C" int printf (const char*, ...);
extern int global_var_name;
extern int local_var_name;
void print ()
{
printf("%d%d\n", global_var_name, local_var_name);
}
и восстановить объектный файл
$ g++ -c src2.cpp -o src2.o
ОК (без ошибок), потому что мы только строим объектный файл, связывание еще не завершено. Попробуй ссылку
$ g++ src1.o src2.o -o prog
src2.o: In function `print()':
src2.cpp:(.text+0x6): undefined reference to `local_var_name'
collect2: error: ld returned 1 exit status
Это произошло потому, что наше local_var_name является статическим, то есть оно не видно для других модулей. Теперь глубже. Получите вывод фазы перевода
$ g++ -S src1.cpp -o src1.s
// src1.s
look src1.s
.file "src1.cpp"
.local _ZL14local_var_name
.comm _ZL14local_var_name,4,4
.globl global_var_name
.data
.align 4
.type global_var_name, @object
.size global_var_name, 4
global_var_name:
.long 123
.text
.globl main
.type main, @function
main:
; assembler code, not interesting for us
.LFE0:
.size main, .-main
.ident "GCC: (Ubuntu 4.8.2-19ubuntu1) 4.8.2"
.section .note.GNU-stack,"",@progbits
Итак, мы видели, что нет метки для local_var_name, поэтому компоновщик не нашел ее. Но мы хакеры :) и мы можем это исправить. Откройте src1.s в вашем текстовом редакторе и измените
.local _ZL14local_var_name
.comm _ZL14local_var_name,4,4
в
.globl local_var_name
.data
.align 4
.type local_var_name, @object
.size local_var_name, 4
local_var_name:
.long 456789
т.е. вы должны иметь как ниже
.file "src1.cpp"
.globl local_var_name
.data
.align 4
.type local_var_name, @object
.size local_var_name, 4
local_var_name:
.long 456789
.globl global_var_name
.align 4
.type global_var_name, @object
.size global_var_name, 4
global_var_name:
.long 123
.text
.globl main
.type main, @function
main:
; ...
мы изменили видимость local_var_name и установили его значение на 456789. Попробуйте создать из него объектный файл
$ g++ -c src1.s -o src2.o
хорошо, смотрите вывод readelf (символы)
$ readelf --symbols src1.o
8: 0000000000000000 4 OBJECT GLOBAL DEFAULT 3 local_var_name
теперь у local_var_name есть Bind GLOBAL (был LOCAL)
ссылка на сайт
$ g++ src1.o src2.o -o prog
и запустить его
$ ./prog
123456789
хорошо, мы взломали это :)
Таким образом, в результате - «неопределенная ссылка / неразрешенная ошибка внешнего символа» происходит, когда компоновщик не может найти глобальные символы в объектных файлах.
Функция (или переменная) void foo()
была определена в программе на C, и вы пытаетесь использовать ее в программе на C ++:
void foo();
int main()
{
foo();
}
Компоновщик C ++ ожидает искажений имен, поэтому вы должны объявить функцию следующим образом:
extern "C" void foo();
int main()
{
foo();
}
Эквивалентно, вместо того, чтобы быть определенным в программе на C, функция (или переменная) void foo()
была определена в C ++, но с привязкой к C:
extern "C" void foo();
и вы пытаетесь использовать его в программе C ++ со связью C ++.
Если вся библиотека включена в заголовочный файл (и была скомпилирована как код C); включение должно быть следующим;
extern "C" {
#include "cheader.h"
}
#ifdef __cplusplus [\n] extern"C" { [\n] #endif
и #ifdef __cplusplus [\n] } [\n] #endif
([\n]
будучи реальным возвратом каретки, но я не могу написать это правильно в комментарии).
extern "C" { #include <myCppHeader.h> }
.
Если ничего не помогает, перекомпилируйте.
Недавно я смог избавиться от нерешенной внешней ошибки в Visual Studio 2012, просто перекомпилировав файл, который нарушил работу. Когда я перестроил, ошибка ушла.
Это обычно происходит, когда две (или более) библиотеки имеют циклическую зависимость. Библиотека A пытается использовать символы из B.lib, а библиотека B пытается использовать символы из A.lib. Ни один не существует, чтобы начать с. Когда вы попытаетесь скомпилировать A, шаг ссылки потерпит неудачу, потому что он не может найти B.lib. A.lib будет сгенерирован, но не dll. Затем вы компилируете B, который преуспеет и сгенерирует B.lib. Перекомпиляция A теперь будет работать, потому что B.lib теперь найден.
MSVS требует от вас указать, какие символы экспортировать и импортировать, используя __declspec(dllexport)
и__declspec(dllimport)
.
Эта двойная функциональность обычно получается с помощью макроса:
#ifdef THIS_MODULE
#define DLLIMPEXP __declspec(dllexport)
#else
#define DLLIMPEXP __declspec(dllimport)
#endif
Макрос THIS_MODULE
будет определен только в модуле, который экспортирует функцию. Таким образом, декларация:
DLLIMPEXP void foo();
расширяется до
__declspec(dllexport) void foo();
и указывает компилятору экспортировать функцию, так как текущий модуль содержит ее определение. Если включить объявление в другой модуль, оно будет расширено до
__declspec(dllimport) void foo();
и сообщает компилятору, что определение находится в одной из библиотек, с которыми вы связаны (см. также 1) ).
Вы можете аналогичные классы импорта / экспорта:
class DLLIMPEXP X
{
};
visibility
и Windows .def
, так как они также влияют на имя и присутствие символа.
.def
файлы. Не стесняйтесь добавлять ответ или редактировать этот.
Это одно из самых запутанных сообщений об ошибках, которые каждый VC ++ программисты видели снова и снова. Давайте сначала проясним ситуацию.
А. Что такое символ? Короче говоря, символ - это имя. Это может быть имя переменной, имя функции, имя класса, имя typedef или что угодно, кроме тех имен и знаков, которые принадлежат языку C ++. Он определяется пользователем или вводится библиотекой зависимостей (другой определяется пользователем).
B. Что является внешним?
В VC ++ каждый исходный файл (.cpp, .c и т. (Обратите внимание, что каждый заголовочный файл, включенный в этот исходный файл, будет предварительно обработан и будет считаться частью этого модуля перевода) Все в модуле перевода рассматривается как внутреннее, все остальное рассматривается как внешнее. В C ++ вы можете ссылаться на внешний символ, используя такие ключевые слова, как extern
, __declspec (dllimport)
и так далее.
C. Что такое «решимость»? Resolve - это термин времени связывания. Во время компоновки компоновщик пытается найти внешнее определение для каждого символа в объектных файлах, которые не могут найти его определение внутри. Объем этого процесса поиска, включая:
Этот процесс поиска называется решимостью.
D. Наконец, почему неразрешенный внешний символ? Если компоновщик не может найти внешнее определение для символа, который не имеет внутреннего определения, он сообщает об ошибке «Неразрешенный внешний символ».
E. Возможные причины LNK2019 : Неразрешенная ошибка внешнего символа. Мы уже знаем, что эта ошибка связана с тем, что компоновщику не удалось найти определение внешних символов, возможные причины можно отсортировать по следующим параметрам:
Например, если у нас есть функция с именем foo, определенная в a.cpp:
int foo()
{
return 0;
}
В b.cpp мы хотим вызвать функцию foo, поэтому добавим
void foo();
чтобы объявить функцию foo () и вызвать ее в другом теле функции, скажем bar()
:
void bar()
{
foo();
}
Теперь, когда вы создаете этот код, вы получите ошибку LNK2019 с жалобой на то, что foo является неразрешенным символом. В этом случае мы знаем, что foo () имеет свое определение в a.cpp, но отличается от того, который мы вызываем (другое возвращаемое значение). Это тот случай, когда определение существует.
Если мы хотим вызвать некоторые функции в библиотеке, но библиотека импорта не добавляется в дополнительный список зависимостей (устанавливается из:) Project | Properties | Configuration Properties | Linker | Input | Additional Dependency
вашего проекта. Теперь компоновщик сообщит LNK2019, так как определение не существует в текущей области поиска.
Неспециализированные шаблоны должны иметь свои определения, видимые для всех единиц перевода, которые их используют. Это означает, что вы не можете отделить определение шаблона от файла реализации. Если вы должны отделить реализацию, обычный обходной путь должен иметь impl
файл, который вы включаете в конец заголовка, который объявляет шаблон. Распространенная ситуация:
template<class T>
struct X
{
void foo();
};
int main()
{
X<int> x;
x.foo();
}
//differentImplementationFile.cpp
template<class T>
void X<T>::foo()
{
}
Чтобы это исправить, вы должны переместить определение X::foo
в файл заголовка или в другое место, видимое для модуля перевода, который его использует.
Специализированные шаблоны могут быть реализованы в файле реализации, и реализация не должна быть видимой, но специализация должна быть предварительно объявлена.
Для дальнейшего объяснения и другого возможного решения (явной реализации) см. Этот вопрос и ответ .
неопределенная ссылка WinMain@16
или похожая «необычная» main()
ссылка на точку входа (особенно длязрительно-студия).
Возможно, вы пропустили выбор правильного типа проекта с вашей фактической IDE. В среде IDE может потребоваться привязать, например, проекты приложений Windows, к такой функции точки входа (как указано в отсутствующей ссылке выше) вместо обычно используемой int main(int argc, char** argv);
подписи.
Если ваша IDE поддерживает простые консольные проекты, вы можете выбрать этот тип проекта вместо проекта Windows.
Здесь case1 и case2 рассматриваются более подробно из реальной проблемы.
WinMain
. Действительные программы на C ++ нуждаются в main
.
Пакет Visual Studio NuGet необходимо обновить для новой версии набора инструментов
Я только что столкнулся с этой проблемой при попытке связать libpng с Visual Studio 2013. Проблема в том, что файл пакета содержал библиотеки только для Visual Studio 2010 и 2012.
Правильное решение - надеяться, что разработчик выпустит обновленный пакет, а затем обновит его, но он сработал для меня, взломав дополнительный параметр для VS2013, указывая на файлы библиотеки VS2012.
Я отредактировал пакет (в packages
папке внутри каталога решения), найдя packagename\build\native\packagename.targets
и внутри этого файла, скопировав все v110
разделы. Я изменил v110
к v120
в полях состояния только очень осторожно , чтобы оставить пути имени файла все как v110
. Это просто позволило Visual Studio 2013 ссылаться на библиотеки на 2012 год, и в этом случае это работало.
Предположим, у вас есть большой проект, написанный на c ++, который имеет тысячу файлов .cpp и тысячу файлов .h. И давайте скажем, что проект также зависит от десяти статических библиотек. Допустим, мы находимся на Windows, и мы строим наш проект в Visual Studio 20xx. Когда вы нажимаете Ctrl + F7 Visual Studio, чтобы начать компиляцию всего решения (предположим, у нас есть только один проект в решении)
В чем смысл компиляции?
Второй этап компиляции выполняется Linker.Linker должен объединить весь объектный файл и, наконец, создать вывод (который может быть исполняемым файлом или библиотекой).
Шаги в Связывание проекта
error LNK2001: unresolved external symbol "void __cdecl foo(void)" (?foo@@YAXXZ)
наблюдение
Как решить эту ошибку
Ошибка времени компиляции:
Ошибка компоновщика
#pragma once
для разрешения компилятору не включать один заголовок, если он уже был включен в текущий .cpp, который компилируетсяУ меня недавно была эта проблема, и оказалось, что это была ошибка в Visual Studio Express 2013 . Мне пришлось удалить исходный файл из проекта и повторно добавить его, чтобы устранить ошибку.
Действия, если вы считаете, что это может быть ошибка в компиляторе / IDE:
Большинство современных линкеров содержат подробный вариант, который печатает в разной степени;
Для gcc и clang; вы обычно добавляете -v -Wl,--verbose
или -v -Wl,-v
в командную строку. Более подробную информацию можно найти здесь;
Для MSVC /VERBOSE
(в частности /VERBOSE:LIB
) добавляется в командную строку ссылки.
/VERBOSE
опции компоновщика .Связанный .lib файл связан с .dll
Я была такая же проблема. Скажем, у меня есть проекты MyProject и TestProject. Я эффективно связал файл lib для MyProject с TestProject. Однако этот файл lib был создан как библиотека DLL для MyProject. Кроме того, я не содержал исходный код для всех методов в MyProject, но только доступ к точкам входа DLL.
Чтобы решить эту проблему, я построил MyProject как LIB и связал TestProject с этим файлом .lib (скопировал и вставил сгенерированный файл .lib в папку TestProject). Затем я могу снова построить MyProject как DLL. Он компилируется, поскольку библиотека, с которой связан TestProject, содержит код для всех методов в классах в MyProject.
Поскольку люди, кажется, обращаются к этому вопросу, когда дело доходит до ошибок компоновщика, я собираюсь добавить это здесь.
Одна из возможных причин ошибок компоновщика в GCC 5.2.0 заключается в том, что теперь по умолчанию выбрана новая библиотека ABI libstdc ++.
Если вы получаете ошибки компоновщика о неопределенных ссылках на символы, которые включают типы в пространстве имен std :: __ cxx11 или теге [abi: cxx11], то это, вероятно, означает, что вы пытаетесь связать вместе объектные файлы, которые были скомпилированы с различными значениями для _GLIBCXX_USE_CXX11_ABI макро. Это обычно происходит при подключении к сторонней библиотеке, которая была скомпилирована с более старой версией GCC. Если сторонняя библиотека не может быть перестроена с новым ABI, вам нужно будет перекомпилировать ваш код со старым ABI.
Так что, если вы внезапно получите ошибки компоновщика при переключении на GCC после 5.1.0, это было бы полезно проверить.
Оболочка вокруг GNU ld, которая не поддерживает скрипты компоновщика
Некоторые файлы .so на самом деле являются сценариями компоновщика GNU ld , например, файл libtbb.so представляет собой текстовый файл ASCII со следующим содержимым:
INPUT (libtbb.so.2)
Некоторые более сложные сборки могут не поддерживать это. Например, если вы включите параметр -v в опциях компилятора, вы увидите, что оболочка mainwin gcc mwdip отбрасывает командные файлы сценария компоновщика в подробный список вывода библиотек для компоновки. Простой способ обойти это - заменить команду ввода сценария компоновщика. вместо этого файл с копией файла (или символической ссылкой), например
cp libtbb.so.2 libtbb.so
Или вы можете заменить аргумент -l на полный путь к .so, например вместо -ltbb
do/home/foo/tbb-4.3/linux/lib/intel64/gcc4.4/libtbb.so.2
libfoo
зависит от libbar
, то ваша связь правильно ставится libfoo
раньше libbar
.undefined reference to
чем - то ошибкой.#include
и фактически определяются в библиотеках, которые вы связываете.Примеры есть на C. Они также могут быть C ++
my_lib.c
#include "my_lib.h"
#include <stdio.h>
void hw(void)
{
puts("Hello World");
}
my_lib.h
#ifndef MY_LIB_H
#define MT_LIB_H
extern void hw(void);
#endif
eg1.c
#include <my_lib.h>
int main()
{
hw();
return 0;
}
Вы строите свою статическую библиотеку:
$ gcc -c -o my_lib.o my_lib.c
$ ar rcs libmy_lib.a my_lib.o
Вы компилируете свою программу:
$ gcc -I. -c -o eg1.o eg1.c
Вы пытаетесь связать это с libmy_lib.a
и терпите неудачу:
$ gcc -o eg1 -L. -lmy_lib eg1.o
eg1.o: In function `main':
eg1.c:(.text+0x5): undefined reference to `hw'
collect2: error: ld returned 1 exit status
Тот же результат, если вы компилируете и ссылаетесь за один шаг, например:
$ gcc -o eg1 -I. -L. -lmy_lib eg1.c
/tmp/ccQk1tvs.o: In function `main':
eg1.c:(.text+0x5): undefined reference to `hw'
collect2: error: ld returned 1 exit status
libz
eg2.c
#include <zlib.h>
#include <stdio.h>
int main()
{
printf("%s\n",zlibVersion());
return 0;
}
Скомпилируйте вашу программу:
$ gcc -c -o eg2.o eg2.c
Попробуйте связать вашу программу с libz
ошибкой:
$ gcc -o eg2 -lz eg2.o
eg2.o: In function `main':
eg2.c:(.text+0x5): undefined reference to `zlibVersion'
collect2: error: ld returned 1 exit status
То же самое, если вы компилируете и ссылаетесь сразу:
$ gcc -o eg2 -I. -lz eg2.c
/tmp/ccxCiGn7.o: In function `main':
eg2.c:(.text+0x5): undefined reference to `zlibVersion'
collect2: error: ld returned 1 exit status
И вариант в примере 2, включающий pkg-config
:
$ gcc -o eg2 $(pkg-config --libs zlib) eg2.o
eg2.o: In function `main':
eg2.c:(.text+0x5): undefined reference to `zlibVersion'
В последовательности объектных файлов и библиотек, которые вы хотите связать для создания своей программы, вы размещаете библиотеки перед объектными файлами, которые к ним относятся. Вам нужно разместить библиотеки после объектных файлов, которые к ним относятся.
Ссылка на пример 1 правильно:
$ gcc -o eg1 eg1.o -L. -lmy_lib
Успех:
$ ./eg1
Hello World
Ссылка на пример 2 правильно:
$ gcc -o eg2 eg2.o -lz
Успех:
$ ./eg2
1.2.8
pkg-config
Правильно свяжите вариант 2 :
$ gcc -o eg2 eg2.o $(pkg-config --libs zlib)
$ ./eg2
1.2.8
Чтение не является обязательным с этого момента .
По умолчанию команда связывания, созданная GCC в вашем дистрибутиве, использует файлы в связке слева направо в последовательности командной строки. Когда он обнаруживает, что файл ссылается на что-то и не содержит определения для него, он будет искать определение в файлах далее справа. Если это в конечном счете находит определение, ссылка разрешена. Если какие-либо ссылки остаются неразрешенными в конце, связывание завершается ошибкой: компоновщик не выполняет поиск в обратном направлении.
Сначала пример 1 со статической библиотекойmy_lib.a
Статическая библиотека - это индексированный архив объектных файлов. Когда компоновщик находит -lmy_lib
в последовательности компоновки и выясняет , что это относится к статической библиотеке ./libmy_lib.a
, он хочет знать, нуждается ли ваша программа в каких-либо объектных файлах libmy_lib.a
.
В нем есть только объектный файл libmy_lib.a
, а my_lib.o
в нем определена только одна вещь my_lib.o
- функция hw
.
Компоновщик решит, что вашей программе нужно, my_lib.o
если и только если он уже знает, на что ссылается ваша программа hw
, в одном или нескольких объектных файлах, которые он уже добавил в программу, и что ни один из уже добавленных объектных файлов не содержит определение для hw
.
Если это так, то компоновщик извлечет копию my_lib.o
из библиотеки и добавит ее в вашу программу. Затем программа содержит определение hw
, поэтому его ссылки на hw
которые разрешаются .
При попытке связать программу, как:
$ gcc -o eg1 -L. -lmy_lib eg1.o
компоновщик не добавил eg1.o
в программу, когда он видит
-lmy_lib
. Потому что на тот момент его еще не видели eg1.o
. Ваша программа еще не делает никаких ссылок на hw
нее: она еще не делает никаких ссылок вообще , потому что все ссылки, которые она делает, находятся в eg1.o
.
Таким образом, компоновщик не добавляет my_lib.o
в программу и не имеет дальнейшего использования libmy_lib.a
.
Затем он находит eg1.o
и добавляет его в программу. Объектный файл в последовательности связей всегда добавляется в программу. Теперь программа ссылается hw
и не содержит определения hw
; но в последовательности связей не осталось ничего, что могло бы дать недостающее определение. Ссылка на hw
оказывается неразрешенной , и связь не выполняется.
Второй, пример 2 , с общей библиотекойlibz
Общая библиотека не является архивом объектных файлов или чем-то подобным. Это больше похоже на программу , которая не имеет main
функции и вместо этого предоставляет множество других символов, которые она определяет, так что другие программы могут использовать их во время выполнения.
Многие Linux дистрибутивов сегодня настроить их GCC набор инструментов так , чтобы его язык водители ( gcc
, g++
, и gfortran
т.д.) проинструктировать систему линкера ( ld
) , чтобы связать разделяемые библиотеки на в качестве необходимой основе. У вас есть один из этих дистрибутивов.
Это означает, что когда компоновщик находит -lz
в последовательности компоновки и выясняет , что это относится к общей библиотеке (скажем) /usr/lib/x86_64-linux-gnu/libz.so
, он хочет знать, имеют ли какие-либо ссылки, которые он добавил в вашу программу, которые еще не определены, определения, которые экспортируетсяlibz
Если это так, то компоновщик не будет копировать какие-либо фрагменты libz
и добавлять их в вашу программу; вместо этого он просто изменит код вашей программы, чтобы:
Во время выполнения загрузчик системной программы будет загружать копию libz
в тот же процесс, что и ваша программа, всякий раз, когда она загружает копию вашей программы, для ее запуска.
Во время выполнения, когда ваша программа ссылается на что-то, что определено в
libz
, эта ссылка использует определение, экспортированное копией libz
в том же процессе.
Ваша программа хочет ссылаться только на одну вещь, для которой экспортировано определение libz
, а именно на функцию zlibVersion
, на которую ссылаются только один раз, в eg2.c
. Если компоновщик добавляет эту ссылку в вашу программу, а затем находит определение, экспортированное с помощью libz
, ссылка разрешается
Но когда вы пытаетесь связать программу, как:
gcc -o eg2 -lz eg2.o
порядок событий неправильный точно так же, как в примере 1. В тот момент, когда компоновщик обнаруживает -lz
, в программе нет ссылок на что-либо: они все находятся внутри eg2.o
, что еще не было замечено. Поэтому компоновщик решает, что он бесполезен libz
. Когда он достигает eg2.o
, добавляет его в программу и затем имеет неопределенную ссылку zlibVersion
, последовательность связывания заканчивается; эта ссылка не разрешена, и связь не работает.
Наконец, pkg-config
вариант примера 2 имеет теперь очевидное объяснение. После расширения оболочки:
gcc -o eg2 $(pkg-config --libs zlib) eg2.o
будет выглядеть так:
gcc -o eg2 -lz eg2.o
это просто пример 2 снова.
Связь:
gcc -o eg2 -lz eg2.o
работает просто отлично для вас!
(Или: эта связь работала у вас, скажем, на Fedora 23, но не работает на Ubuntu 16.04)
Это связано с тем, что дистрибутив, на котором работает связывание, является одним из тех, которые не настраивают свой набор инструментов GCC для связывания разделяемых библиотек по мере необходимости .
Раньше для unix-подобных систем было нормальным связывать статические и разделяемые библиотеки по разным правилам. Статические библиотеки в последовательности связывания были связаны по мере необходимости, объясненной в примере 1, но совместно используемые библиотеки были связаны безоговорочно.
Такое поведение экономично во время компоновки, поскольку компоновщику не нужно задумываться о том, нужна ли программе общая библиотека: если это общая библиотека, свяжите ее. И большинство библиотек в большинстве связей являются общими библиотеками. Но есть и недостатки:
Это неэкономично во время выполнения , поскольку может привести к загрузке совместно используемых библиотек вместе с программой, даже если они не нужны.
Различные правила связывания для статических и разделяемых библиотек могут сбивать с толку неопытных программистов, которые могут не знать, будет ли -lfoo
в их связывании разрешаться /some/where/libfoo.a
или нет /some/where/libfoo.so
, и могут все равно не понимать разницу между разделяемыми и статическими библиотеками.
Этот компромисс привел к раскольнической ситуации сегодня. Некоторые дистрибутивы изменили свои правила связывания GCC для разделяемых библиотек, так что принцип по мере необходимости применяется ко всем библиотекам. Некоторые дистрибутивы застряли по-старому.
Если я просто сделаю:
$ gcc -o eg1 -I. -L. -lmy_lib eg1.c
конечно, gcc eg1.c
сначала должен скомпилировать , а затем связать полученный объектный файл с libmy_lib.a
. Так как же он не может знать, что объектный файл нужен, когда он выполняет связывание?
Поскольку компиляция и компоновка с помощью одной команды не изменяют порядок последовательности компоновки.
Когда вы запустите команду выше, gcc
выясните, что вам нужна компиляция + компоновка. Так что за кулисами он генерирует команду компиляции и запускает ее, затем генерирует команду связывания и запускает ее, как если бы вы выполнили две команды:
$ gcc -I. -c -o eg1.o eg1.c
$ gcc -o eg1 -L. -lmy_lib eg1.o
Таким образом, связь не может так же , как это делает , если вы действительно запустить эти две команды. Единственное отличие, которое вы замечаете в ошибке, состоит в том, что gcc сгенерировал временный объектный файл в случае компиляции + ссылки, потому что вы не говорите ему использовать eg1.o
. Мы видим:
/tmp/ccQk1tvs.o: In function `main'
вместо:
eg1.o: In function `main':
Порядок, в котором указаны взаимозависимые связанные библиотеки, неверен
Размещение взаимозависимых библиотек в неправильном порядке - это всего лишь один из способов, с помощью которого вы можете получить файлы, которые требуют определений вещей, которые появятся позже в связке, чем файлы, которые предоставляют определения. Размещение библиотек перед объектными файлами, которые ссылаются на них, является еще одним способом сделать ту же ошибку.
Дан фрагмент кода типа шаблона с оператором друга (или функцией);
template <typename T>
class Foo {
friend std::ostream& operator<< (std::ostream& os, const Foo<T>& a);
};
Объект operator<<
объявляется как не шаблонная функция. Для каждого типа, T
используемого с Foo
, должен быть не шаблон operator<<
. Например, если Foo<int>
объявлен тип , то должна быть реализация оператора следующим образом;
std::ostream& operator<< (std::ostream& os, const Foo<int>& a) {/*...*/}
Поскольку это не реализовано, компоновщик не может найти его и приводит к ошибке.
Чтобы исправить это, вы можете объявить оператор шаблона перед Foo
типом, а затем объявить в качестве друга соответствующий экземпляр. Синтаксис немного неудобен, но выглядит следующим образом;
// forward declare the Foo
template <typename>
class Foo;
// forward declare the operator <<
template <typename T>
std::ostream& operator<<(std::ostream&, const Foo<T>&);
template <typename T>
class Foo {
friend std::ostream& operator<< <>(std::ostream& os, const Foo<T>& a);
// note the required <> ^^^^
// ...
};
template <typename T>
std::ostream& operator<<(std::ostream&, const Foo<T>&)
{
// ... implement the operator
}
Вышеприведенный код ограничивает дружбу оператора соответствующим экземпляром Foo
, то есть operator<< <int>
экземпляр ограничен доступом к закрытым членам экземпляра Foo<int>
.
Альтернативы включают в себя;
Позволяя дружбе распространяться на все экземпляры шаблонов следующим образом;
template <typename T>
class Foo {
template <typename T1>
friend std::ostream& operator<<(std::ostream& os, const Foo<T1>& a);
// ...
};
Или, реализация для operator<<
может быть встроена внутри определения класса;
template <typename T>
class Foo {
friend std::ostream& operator<<(std::ostream& os, const Foo& a)
{ /*...*/ }
// ...
};
Обратите внимание : когда объявление оператора (или функции) появляется только в классе, имя недоступно для «нормального» поиска, только для поиска, зависящего от аргумента, из cppreference ;
Имя, впервые объявленное в объявлении друга в классе или шаблоне класса X, становится членом внутреннего вложенного пространства имен X, но недоступно для поиска (за исключением поиска, зависящего от аргументов, который учитывает X), если только соответствующее объявление в области пространства имен не является при условии...
Дальнейшее чтение о друзьях шаблонов можно найти в cppreference и в C ++ FAQ .
Список кодов, показывающий методы выше .
Как примечание к ошибочному образцу кода; G ++ предупреждает об этом следующим образом
warning: friend declaration 'std::ostream& operator<<(...)' declares a non-template function [-Wnon-template-friend]
note: (if this is not what you intended, make sure the function template has already been declared and add <> after the function name here)
Ошибки компоновщика могут возникать, когда файл заголовка и связанная с ним общая библиотека (файл .lib) не синхронизируются. Позволь мне объяснить.
Как работают линкеры? Компоновщик сопоставляет объявление функции (объявленное в заголовке) с ее определением (в общей библиотеке), сравнивая их сигнатуры. Вы можете получить ошибку компоновщика, если компоновщик не найдет определение функции, которое идеально соответствует.
Возможно ли все еще получить ошибку компоновщика, даже если объявление и определение, кажется, совпадают? Да! Они могут выглядеть одинаково в исходном коде, но это действительно зависит от того, что видит компилятор. По сути, вы можете получить такую ситуацию:
// header1.h
typedef int Number;
void foo(Number);
// header2.h
typedef float Number;
void foo(Number); // this only looks the same lexically
Обратите внимание, что хотя оба объявления функций выглядят одинаково в исходном коде, но они действительно различаются в зависимости от компилятора.
Вы можете спросить, как человек попадает в такую ситуацию? Включите пути конечно! Если при компиляции разделяемой библиотеки путь включения приводит к тому, что header1.h
вы в конечном итоге используете ее header2.h
в своей программе, вам останется поцарапать заголовок, задаваясь вопросом, что произошло (каламбур предназначен).
Пример того, как это может произойти в реальном мире, объясняется ниже.
У меня есть два проекта: graphics.lib
и main.exe
. Оба проекта зависят от common_math.h
. Предположим, библиотека экспортирует следующую функцию:
// graphics.lib
#include "common_math.h"
void draw(vec3 p) { ... } // vec3 comes from common_math.h
И тогда вы идете дальше и включаете библиотеку в свой собственный проект.
// main.exe
#include "other/common_math.h"
#include "graphics.h"
int main() {
draw(...);
}
Boom! Вы получаете ошибку компоновщика, и вы не знаете, почему он не работает. Причина в том, что общая библиотека использует разные версии одного и того же include common_math.h
(я продемонстрировал здесь, в примере, включив другой путь, но это не всегда так очевидно. Возможно, путь включения отличается в настройках компилятора) ,
Обратите внимание, что в этом примере компоновщик скажет вам, что он не может найти draw()
, когда на самом деле вы знаете, что он явно экспортируется библиотекой. Вы могли бы часами чесать голову, задаваясь вопросом, что пошло не так. Дело в том, что компоновщик видит другую подпись, потому что типы параметров немного отличаются. В этом примере vec3
это другой тип в обоих проектах, поскольку это касается компилятора. Это может произойти, потому что они приходят из двух слегка отличающихся файлов включений (возможно, файлы включений происходят из двух разных версий библиотеки).
DUMPBIN - ваш друг, если вы используете Visual Studio. Я уверен, что другие компиляторы имеют другие подобные инструменты.
Процесс идет так:
[1] Под проектом я подразумеваю набор исходных файлов, которые связаны вместе для создания библиотеки или исполняемого файла.
РЕДАКТИРОВАТЬ 1: переписал первый раздел, чтобы было легче понять. Пожалуйста, прокомментируйте ниже, чтобы сообщить мне, если что-то еще нужно исправить. Спасибо!
UNICODE
определенияСборка Windows UNICODE строится так, чтобы TCHAR
и т. Д. Определялась как wchar_t
и т. Д. Если не строится с помощью UNICODE
определения, как сборка, с TCHAR
определением как char
и т. Д. Эти UNICODE
и _UNICODE
определения влияют на все " T
" строковые типы ; LPTSTR
, LPCTSTR
И их лосей.
Создание одной библиотеки с UNICODE
определенной и попытка связать ее в проекте, где UNICODE
она не определена, приведет к ошибкам компоновщика, поскольку в определении будет несоответствие TCHAR
; char
против wchar_t
.
Ошибка обычно включает в себя функцию со значением char
или wchar_t
производным типом, они также могут включать std::basic_string<>
и т. Д. При просмотре уязвимой функции в коде часто встречается ссылка на TCHAR
и std::basic_string<TCHAR>
т. Д. Это контрольный признак того, что код изначально предназначался как для сборки UNICODE, так и для многобайтовых символов (или «узких»). ,
Чтобы исправить это, соберите все необходимые библиотеки и проекты с непротиворечивым определением UNICODE
(и _UNICODE
).
Это может быть сделано с любой;
#define UNICODE
#define _UNICODE
Или в настройках проекта;
Свойства проекта> Общие> Проект по умолчанию> Набор символов
Или в командной строке;
/DUNICODE /D_UNICODE
Альтернатива также применима, если UNICODE не предназначен для использования, убедитесь, что определения не установлены, и / или многосимвольный параметр используется в проектах и применяется последовательно.
Не забывайте также соблюдать согласованность между сборками «Release» и «Debug».
«Чистая» сборка может удалить «мертвую древесину», которая может быть оставлена на месте из предыдущих сборок, неудачных сборок, неполных сборок и других проблем сборки, связанных с системой сборки.
В общем случае IDE или сборка будут включать в себя некоторую форму «чистой» функции, но это может быть неправильно настроено (например, в файле ручной сборки) или может завершиться ошибкой (например, промежуточные или результирующие двоичные файлы доступны только для чтения).
После завершения «clean» убедитесь, что «clean» завершился успешно и все сгенерированные промежуточные файлы (например, автоматический make-файл) были успешно удалены.
Этот процесс можно рассматривать как последнее средство, но часто это хороший первый шаг ; особенно если недавно был добавлен код, связанный с ошибкой (локально или из исходного репозитория).
const
объявлениях / определениях переменных (только C ++)Для людей, пришедших из C, может быть удивительным, что в C ++ глобальные const
переменные имеют внутреннюю (или статическую) связь. В Си это было не так, поскольку все глобальные переменные неявно extern
(то есть, когда static
ключевое слово отсутствует).
Пример:
// file1.cpp
const int test = 5; // in C++ same as "static const int test = 5"
int test2 = 5;
// file2.cpp
extern const int test;
extern int test2;
void foo()
{
int x = test; // linker error in C++ , no error in C
int y = test2; // no problem
}
правильно будет использовать заголовочный файл и включить его в file2.cpp и file1.cpp
extern const int test;
extern int test2;
В качестве альтернативы можно объявить const
переменную в file1.cpp с явнымextern
Несмотря на то, что это довольно старые вопросы с несколькими принятыми ответами, я хотел бы поделиться тем, как устранить скрытую ошибку «неопределенная ссылка на».
Я использовал псевдоним для ссылки std::filesystem::path
: файловая система находится в стандартной библиотеке начиная с C ++ 17, но моя программа должна была также компилироваться в C ++ 14, поэтому я решил использовать переменный псевдоним:
#if (defined _GLIBCXX_EXPERIMENTAL_FILESYSTEM) //is the included filesystem library experimental? (C++14 and newer: <experimental/filesystem>)
using path_t = std::experimental::filesystem::path;
#elif (defined _GLIBCXX_FILESYSTEM) //not experimental (C++17 and newer: <filesystem>)
using path_t = std::filesystem::path;
#endif
Допустим, у меня есть три файла: main.cpp, file.h, file.cpp:
Обратите внимание на различные библиотеки, используемые в main.cpp и file.h. Поскольку main.cpp # include'd « file.h » после < filesystem >, используемая там версия файловой системы была C ++ 17 . Я использовал для компиляции программы с помощью следующих команд:
$ g++ -g -std=c++17 -c main.cpp
-> компилирует main.cpp в main.o
$ g++ -g -std=c++17 -c file.cpp
-> компилирует file.cpp и file.h в file.o
$ g++ -g -std=c++17 -o executable main.o file.o -lstdc++fs
-> связывает main.o и file.o
Таким образом , любая функция , содержащаяся в file.o и используется в main.o , что требуетсяpath_t
дал «неопределенная ссылка» ошибки , потому что main.o называют , std::filesystem::path
но file.o к std::experimental::filesystem::path
.
Чтобы это исправить, мне просто нужно было изменить <экспериментальный :: файловая система> в file.h на <файловая система> .
Поведение gcc по умолчанию состоит в том, что все символы видны. Однако, когда единицы перевода построены с опцией -fvisibility=hidden
, только функции / символы, помеченные значком, __attribute__ ((visibility ("default")))
являются внешними в результирующем общем объекте.
Вы можете проверить, являются ли символы, которые вы ищете, внешними, вызвав:
# -D shows (global) dynamic symbols that can be used from the outside of XXX.so
nm -D XXX.so | grep MY_SYMBOL
скрытые / локальные символы nm
обозначаются строчными символами, например t
вместо `T для секции кода:
nm XXX.so
00000000000005a7 t HIDDEN_SYMBOL
00000000000005f8 T VISIBLE_SYMBOL
Вы также можете использовать nm
с опцией, -C
чтобы разобрать имена (если использовался C ++).
Как и в Windows-DLL, можно пометить публичные функции с помощью определения, например, DLL_PUBLIC
определенного как:
#define DLL_PUBLIC __attribute__ ((visibility ("default")))
DLL_PUBLIC int my_public_function(){
...
}
Что примерно соответствует Windows / MSVC-версии:
#ifdef BUILDING_DLL
#define DLL_PUBLIC __declspec(dllexport)
#else
#define DLL_PUBLIC __declspec(dllimport)
#endif
Более подробную информацию о видимости можно найти в gcc wiki.
Когда модуль перевода скомпилирован с -fvisibility=hidden
результирующими символами, они все еще имеют внешнюю связь (показывается символом верхнего регистра в виде символа nm
) и могут без проблем использоваться для внешней связи, если объектные файлы становятся частью статических библиотек. Связь становится локальной, только когда объектные файлы связаны в общей библиотеке.
Чтобы определить, какие символы в объектном файле скрыты, выполните:
>>> objdump -t XXXX.o | grep hidden
0000000000000000 g F .text 000000000000000b .hidden HIDDEN_SYMBOL1
000000000000000b g F .text 000000000000000b .hidden HIDDEN_SYMBOL2
Разные архитектуры
Вы можете увидеть сообщение как:
library machine type 'x64' conflicts with target machine type 'X86'
В этом случае это означает, что доступные символы для другой архитектуры, чем та, для которой вы компилируете.
В Visual Studio это происходит из-за неправильной «Платформы», и вам нужно либо выбрать подходящую, либо установить правильную версию библиотеки.
В Linux это может быть связано с неправильной папкой библиотеки (используется lib
вместо, lib64
например).
В MacOS есть возможность отправить обе архитектуры в одном файле. Может быть так, что ссылка ожидает, что обе версии будут там, но только одна есть. Это также может быть проблема с неправильной папкой lib
/, в lib64
которой находится библиотека.