Что такое неопределенные ссылки / неразрешенные внешние ошибки символов? Каковы общие причины и как их исправить / предотвратить?
Не стесняйтесь редактировать / добавлять свои собственные.
c++
linker-errors
undefined-reference
c++-faq
unresolved-external
Лучиан Григоре
источник
источник
Ответы:
Компиляция программы на C ++ происходит в несколько этапов, как указано в 2.2 (благодарность Кита Томпсону за справку) :
Указанные ошибки возникают на этом последнем этапе компиляции, который чаще всего называют связыванием. По сути, это означает, что вы скомпилировали кучу файлов реализации в объектные файлы или библиотеки, и теперь вы хотите, чтобы они работали вместе.
Скажем, вы определили символ
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 .Код:
сгенерирует следующие ошибки с GCC :
и подобные ошибки с Microsoft Visual Studio :
Общие причины включают в себя:
#pragma
(Microsoft Visual Studio)UNICODE
определенияисточник
Члены класса:
Чистому
virtual
деструктору нужна реализация.Чтобы объявить деструктор чистым, вы все равно должны его определить (в отличие от обычной функции):
Это происходит потому, что деструкторы базового класса вызываются, когда объект уничтожается неявно, поэтому требуется определение.
virtual
методы должны быть либо реализованы, либо определены как чистые.Это похоже на
virtual
методы без определения, с дополнительным обоснованием того, что чистое объявление генерирует фиктивную vtable, и вы можете получить ошибку компоновщика без использования функции:Чтобы это работало, объявляем
X::foo()
чистым:Номера для
virtual
классаНекоторые члены должны быть определены, даже если они не используются явно:
Следующее приведет к ошибке:
Реализация может быть встроенной в самом определении класса:
или снаружи:
Если реализация находится за пределами определения класса, но в заголовке, методы должны быть помечены как
inline
предотвращающие множественное определение.Все используемые методы-члены должны быть определены, если они используются.
Распространенная ошибка - забыть назвать название:
Определение должно быть
static
члены данных должны быть определены вне класса в одной единице перевода :Инициализатор может быть предоставлен для
static
const
члена данных целочисленного или перечислимого типа в определении класса; однако использование odr этого члена все равно потребует определения области имен, как описано выше. C ++ 11 позволяет инициализацию внутри класса для всехstatic const
членов данных.источник
Ошибка связывания с соответствующими библиотеками / объектными файлами или компиляцией файлов реализации.
Обычно каждая единица перевода генерирует объектный файл, который содержит определения символов, определенных в этой единице перевода. Чтобы использовать эти символы, вы должны ссылаться на эти объектные файлы.
В gcc вы должны указать все объектные файлы, которые должны быть связаны вместе в командной строке, или скомпилировать файлы реализации вместе.
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).Объявлен, но не определил переменную или функцию.
Типичное объявление переменной
Поскольку это только декларация, необходимо одно определение . Соответствующее определение будет:
Например, следующее может привести к ошибке:
Аналогичные замечания относятся к функциям. Объявление функции без ее определения приводит к ошибке:
Будьте осторожны, чтобы реализуемая вами функция точно соответствовала той, которую вы объявили. Например, вы могли не соответствовать cv-квалификаторам:
Другие примеры несоответствий включают
Сообщение об ошибке от компилятора часто дает вам полное объявление переменной или функции, которая была объявлена, но никогда не определялась. Сравните это с приведенным вами определением. Убедитесь, что каждая деталь соответствует.
источник
#includes
не добавленными в исходный каталог, также попадают в категорию отсутствующих определений.Порядок, в котором указываются взаимозависимые связанные библиотеки, неверен.
Порядок, в котором связаны библиотеки, имеет значение, если библиотеки зависят друг от друга. Как правило, если библиотека
A
зависит от библиотекиB
, онаlibA
ДОЛЖНА появляться раньшеlibB
в флагах компоновщика.Например:
Создайте библиотеки:
Обобщение:
Так что еще раз повторить, порядок ДЕЛАЕТ дело!
источник
что такое «неопределенная ссылка / неразрешенный внешний символ»
Я попытаюсь объяснить, что такое «неопределенная ссылка / неразрешенный внешний символ».
Например, у нас есть код
а также
Сделать объектные файлы
После фазы ассемблера у нас есть объектный файл, который содержит любые символы для экспорта. Посмотрите на символы
Я отклонил некоторые строки из вывода, потому что они не имеют значения
Итак, мы видим следующие символы для экспорта.
src2.cpp ничего не экспортирует, и мы не видели его символов
Связать наши объектные файлы
и запустить его
Линкер видит экспортированные символы и связывает их. Теперь мы пытаемся раскомментировать строки в src2.cpp, как здесь
и восстановить объектный файл
ОК (без ошибок), потому что мы только строим объектный файл, связывание еще не завершено. Попробуй ссылку
Это произошло потому, что наше local_var_name является статическим, то есть оно не видно для других модулей. Теперь глубже. Получите вывод фазы перевода
Итак, мы видели, что нет метки для local_var_name, поэтому компоновщик не нашел ее. Но мы хакеры :) и мы можем это исправить. Откройте src1.s в вашем текстовом редакторе и измените
в
т.е. вы должны иметь как ниже
мы изменили видимость local_var_name и установили его значение на 456789. Попробуйте создать из него объектный файл
хорошо, смотрите вывод readelf (символы)
теперь у local_var_name есть Bind GLOBAL (был LOCAL)
ссылка на сайт
и запустить его
хорошо, мы взломали это :)
Таким образом, в результате - «неопределенная ссылка / неразрешенная ошибка внешнего символа» происходит, когда компоновщик не может найти глобальные символы в объектных файлах.
источник
Символы были определены в программе на C и использованы в коде C ++.
Функция (или переменная)
void foo()
была определена в программе на C, и вы пытаетесь использовать ее в программе на C ++:Компоновщик C ++ ожидает искажений имен, поэтому вы должны объявить функцию следующим образом:
Эквивалентно, вместо того, чтобы быть определенным в программе на C, функция (или переменная)
void foo()
была определена в C ++, но с привязкой к C:и вы пытаетесь использовать его в программе C ++ со связью C ++.
Если вся библиотека включена в заголовочный файл (и была скомпилирована как код C); включение должно быть следующим;
источник
#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 теперь найден.
источник
Неправильный импорт / экспорт методов / классов через модули / dll (зависит от компилятора).
MSVS требует от вас указать, какие символы экспортировать и импортировать, используя
__declspec(dllexport)
и__declspec(dllimport)
.Эта двойная функциональность обычно получается с помощью макроса:
Макрос
THIS_MODULE
будет определен только в модуле, который экспортирует функцию. Таким образом, декларация:расширяется до
и указывает компилятору экспортировать функцию, так как текущий модуль содержит ее определение. Если включить объявление в другой модуль, оно будет расширено до
и сообщает компилятору, что определение находится в одной из библиотек, с которыми вы связаны (см. также 1) ).
Вы можете аналогичные классы импорта / экспорта:
источник
visibility
и Windows.def
, так как они также влияют на имя и присутствие символа..def
файлы. Не стесняйтесь добавлять ответ или редактировать этот.Это одно из самых запутанных сообщений об ошибках, которые каждый VC ++ программисты видели снова и снова. Давайте сначала проясним ситуацию.
А. Что такое символ? Короче говоря, символ - это имя. Это может быть имя переменной, имя функции, имя класса, имя typedef или что угодно, кроме тех имен и знаков, которые принадлежат языку C ++. Он определяется пользователем или вводится библиотекой зависимостей (другой определяется пользователем).
B. Что является внешним? В VC ++ каждый исходный файл (.cpp, .c и т. (Обратите внимание, что каждый заголовочный файл, включенный в этот исходный файл, будет предварительно обработан и будет считаться частью этого модуля перевода) Все в модуле перевода рассматривается как внутреннее, все остальное рассматривается как внешнее. В C ++ вы можете ссылаться на внешний символ, используя такие ключевые слова, как
extern
,__declspec (dllimport)
и так далее.C. Что такое «решимость»? Resolve - это термин времени связывания. Во время компоновки компоновщик пытается найти внешнее определение для каждого символа в объектных файлах, которые не могут найти его определение внутри. Объем этого процесса поиска, включая:
Этот процесс поиска называется решимостью.
D. Наконец, почему неразрешенный внешний символ? Если компоновщик не может найти внешнее определение для символа, который не имеет внутреннего определения, он сообщает об ошибке «Неразрешенный внешний символ».
E. Возможные причины LNK2019 : Неразрешенная ошибка внешнего символа. Мы уже знаем, что эта ошибка связана с тем, что компоновщику не удалось найти определение внешних символов, возможные причины можно отсортировать по следующим параметрам:
Например, если у нас есть функция с именем foo, определенная в a.cpp:
В b.cpp мы хотим вызвать функцию foo, поэтому добавим
чтобы объявить функцию foo () и вызвать ее в другом теле функции, скажем
bar()
:Теперь, когда вы создаете этот код, вы получите ошибку LNK2019 с жалобой на то, что foo является неразрешенным символом. В этом случае мы знаем, что foo () имеет свое определение в a.cpp, но отличается от того, который мы вызываем (другое возвращаемое значение). Это тот случай, когда определение существует.
Если мы хотим вызвать некоторые функции в библиотеке, но библиотека импорта не добавляется в дополнительный список зависимостей (устанавливается из:)
Project | Properties | Configuration Properties | Linker | Input | Additional Dependency
вашего проекта. Теперь компоновщик сообщит LNK2019, так как определение не существует в текущей области поиска.источник
Реализации шаблона не видны.
Неспециализированные шаблоны должны иметь свои определения, видимые для всех единиц перевода, которые их используют. Это означает, что вы не можете отделить определение шаблона от файла реализации. Если вы должны отделить реализацию, обычный обходной путь должен иметь
impl
файл, который вы включаете в конец заголовка, который объявляет шаблон. Распространенная ситуация:Чтобы это исправить, вы должны переместить определение
X::foo
в файл заголовка или в другое место, видимое для модуля перевода, который его использует.Специализированные шаблоны могут быть реализованы в файле реализации, и реализация не должна быть видимой, но специализация должна быть предварительно объявлена.
Для дальнейшего объяснения и другого возможного решения (явной реализации) см. Этот вопрос и ответ .
источник
неопределенная ссылка
WinMain@16
или похожая «необычная»main()
ссылка на точку входа (особенно длязрительно-студия).Возможно, вы пропустили выбор правильного типа проекта с вашей фактической IDE. В среде IDE может потребоваться привязать, например, проекты приложений Windows, к такой функции точки входа (как указано в отсутствующей ссылке выше) вместо обычно используемой
int main(int argc, char** argv);
подписи.Если ваша IDE поддерживает простые консольные проекты, вы можете выбрать этот тип проекта вместо проекта Windows.
Здесь case1 и case2 рассматриваются более подробно из реальной проблемы.
источник
WinMain
. Действительные программы на C ++ нуждаются вmain
.Также, если вы используете сторонние библиотеки, убедитесь, что у вас есть правильные 32/64-битные двоичные файлы
источник
Microsoft предлагает
#pragma
ссылаться на правильную библиотеку во время ссылки;В дополнение к пути к библиотеке, включая каталог библиотеки, это должно быть полное имя библиотеки.
источник
Пакет 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, который компилируетсяисточник
Ошибка в компиляторе / IDE
У меня недавно была эта проблема, и оказалось, что это была ошибка в 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 ++.
Так что, если вы внезапно получите ошибки компоновщика при переключении на GCC после 5.1.0, это было бы полезно проверить.
источник
Оболочка вокруг GNU ld, которая не поддерживает скрипты компоновщика
Некоторые файлы .so на самом деле являются сценариями компоновщика GNU ld , например, файл libtbb.so представляет собой текстовый файл ASCII со следующим содержимым:
Некоторые более сложные сборки могут не поддерживать это. Например, если вы включите параметр -v в опциях компилятора, вы увидите, что оболочка mainwin gcc mwdip отбрасывает командные файлы сценария компоновщика в подробный список вывода библиотек для компоновки. Простой способ обойти это - заменить команду ввода сценария компоновщика. вместо этого файл с копией файла (или символической ссылкой), например
Или вы можете заменить аргумент -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
my_lib.h
eg1.c
Вы строите свою статическую библиотеку:
Вы компилируете свою программу:
Вы пытаетесь связать это с
libmy_lib.a
и терпите неудачу:Тот же результат, если вы компилируете и ссылаетесь за один шаг, например:
Минимальный пример, включающий общую системную библиотеку, библиотеку сжатия
libz
eg2.c
Скомпилируйте вашу программу:
Попробуйте связать вашу программу с
libz
ошибкой:То же самое, если вы компилируете и ссылаетесь сразу:
И вариант в примере 2, включающий
pkg-config
:Что ты делаешь не так?
В последовательности объектных файлов и библиотек, которые вы хотите связать для создания своей программы, вы размещаете библиотеки перед объектными файлами, которые к ним относятся. Вам нужно разместить библиотеки после объектных файлов, которые к ним относятся.
Ссылка на пример 1 правильно:
Успех:
Ссылка на пример 2 правильно:
Успех:
pkg-config
Правильно свяжите вариант 2 :Объяснение
Чтение не является обязательным с этого момента .
По умолчанию команда связывания, созданная 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
которые разрешаются .При попытке связать программу, как:
компоновщик не добавил
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
, ссылка разрешаетсяНо когда вы пытаетесь связать программу, как:
порядок событий неправильный точно так же, как в примере 1. В тот момент, когда компоновщик обнаруживает
-lz
, в программе нет ссылок на что-либо: они все находятся внутриeg2.o
, что еще не было замечено. Поэтому компоновщик решает, что он бесполезенlibz
. Когда он достигаетeg2.o
, добавляет его в программу и затем имеет неопределенную ссылкуzlibVersion
, последовательность связывания заканчивается; эта ссылка не разрешена, и связь не работает.Наконец,
pkg-config
вариант примера 2 имеет теперь очевидное объяснение. После расширения оболочки:будет выглядеть так:
это просто пример 2 снова.
Я могу воспроизвести проблему в примере 1, но не в примере 2
Связь:
работает просто отлично для вас!
(Или: эта связь работала у вас, скажем, на Fedora 23, но не работает на Ubuntu 16.04)
Это связано с тем, что дистрибутив, на котором работает связывание, является одним из тех, которые не настраивают свой набор инструментов GCC для связывания разделяемых библиотек по мере необходимости .
Раньше для unix-подобных систем было нормальным связывать статические и разделяемые библиотеки по разным правилам. Статические библиотеки в последовательности связывания были связаны по мере необходимости, объясненной в примере 1, но совместно используемые библиотеки были связаны безоговорочно.
Такое поведение экономично во время компоновки, поскольку компоновщику не нужно задумываться о том, нужна ли программе общая библиотека: если это общая библиотека, свяжите ее. И большинство библиотек в большинстве связей являются общими библиотеками. Но есть и недостатки:
Это неэкономично во время выполнения , поскольку может привести к загрузке совместно используемых библиотек вместе с программой, даже если они не нужны.
Различные правила связывания для статических и разделяемых библиотек могут сбивать с толку неопытных программистов, которые могут не знать, будет ли
-lfoo
в их связывании разрешаться/some/where/libfoo.a
или нет/some/where/libfoo.so
, и могут все равно не понимать разницу между разделяемыми и статическими библиотеками.Этот компромисс привел к раскольнической ситуации сегодня. Некоторые дистрибутивы изменили свои правила связывания GCC для разделяемых библиотек, так что принцип по мере необходимости применяется ко всем библиотекам. Некоторые дистрибутивы застряли по-старому.
Почему я все еще получаю эту проблему, даже если я одновременно компилирую и компоноваю?
Если я просто сделаю:
конечно, gcc
eg1.c
сначала должен скомпилировать , а затем связать полученный объектный файл сlibmy_lib.a
. Так как же он не может знать, что объектный файл нужен, когда он выполняет связывание?Поскольку компиляция и компоновка с помощью одной команды не изменяют порядок последовательности компоновки.
Когда вы запустите команду выше,
gcc
выясните, что вам нужна компиляция + компоновка. Так что за кулисами он генерирует команду компиляции и запускает ее, затем генерирует команду связывания и запускает ее, как если бы вы выполнили две команды:Таким образом, связь не может так же , как это делает , если вы действительно запустить эти две команды. Единственное отличие, которое вы замечаете в ошибке, состоит в том, что gcc сгенерировал временный объектный файл в случае компиляции + ссылки, потому что вы не говорите ему использовать
eg1.o
. Мы видим:вместо:
Смотрите также
Порядок, в котором указаны взаимозависимые связанные библиотеки, неверен
Размещение взаимозависимых библиотек в неправильном порядке - это всего лишь один из способов, с помощью которого вы можете получить файлы, которые требуют определений вещей, которые появятся позже в связке, чем файлы, которые предоставляют определения. Размещение библиотек перед объектными файлами, которые ссылаются на них, является еще одним способом сделать ту же ошибку.
источник
Шаблоны для друзей ...
Дан фрагмент кода типа шаблона с оператором друга (или функцией);
Объект
operator<<
объявляется как не шаблонная функция. Для каждого типа,T
используемого сFoo
, должен быть не шаблонoperator<<
. Например, еслиFoo<int>
объявлен тип , то должна быть реализация оператора следующим образом;Поскольку это не реализовано, компоновщик не может найти его и приводит к ошибке.
Чтобы исправить это, вы можете объявить оператор шаблона перед
Foo
типом, а затем объявить в качестве друга соответствующий экземпляр. Синтаксис немного неудобен, но выглядит следующим образом;Вышеприведенный код ограничивает дружбу оператора соответствующим экземпляром
Foo
, то естьoperator<< <int>
экземпляр ограничен доступом к закрытым членам экземпляраFoo<int>
.Альтернативы включают в себя;
Позволяя дружбе распространяться на все экземпляры шаблонов следующим образом;
Или, реализация для
operator<<
может быть встроена внутри определения класса;Обратите внимание : когда объявление оператора (или функции) появляется только в классе, имя недоступно для «нормального» поиска, только для поиска, зависящего от аргумента, из cppreference ;
Дальнейшее чтение о друзьях шаблонов можно найти в cppreference и в C ++ FAQ .
Список кодов, показывающий методы выше .
Как примечание к ошибочному образцу кода; G ++ предупреждает об этом следующим образом
источник
Когда ваши пути включения отличаются
Ошибки компоновщика могут возникать, когда файл заголовка и связанная с ним общая библиотека (файл .lib) не синхронизируются. Позволь мне объяснить.
Как работают линкеры? Компоновщик сопоставляет объявление функции (объявленное в заголовке) с ее определением (в общей библиотеке), сравнивая их сигнатуры. Вы можете получить ошибку компоновщика, если компоновщик не найдет определение функции, которое идеально соответствует.
Возможно ли все еще получить ошибку компоновщика, даже если объявление и определение, кажется, совпадают? Да! Они могут выглядеть одинаково в исходном коде, но это действительно зависит от того, что видит компилятор. По сути, вы можете получить такую ситуацию:
Обратите внимание, что хотя оба объявления функций выглядят одинаково в исходном коде, но они действительно различаются в зависимости от компилятора.
Вы можете спросить, как человек попадает в такую ситуацию? Включите пути конечно! Если при компиляции разделяемой библиотеки путь включения приводит к тому, что
header1.h
вы в конечном итоге используете ееheader2.h
в своей программе, вам останется поцарапать заголовок, задаваясь вопросом, что произошло (каламбур предназначен).Пример того, как это может произойти в реальном мире, объясняется ниже.
Дальнейшая разработка с примером
У меня есть два проекта:
graphics.lib
иmain.exe
. Оба проекта зависят отcommon_math.h
. Предположим, библиотека экспортирует следующую функцию:И тогда вы идете дальше и включаете библиотеку в свой собственный проект.
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
).Это может быть сделано с любой;
Или в настройках проекта;
Или в командной строке;
Альтернатива также применима, если UNICODE не предназначен для использования, убедитесь, что определения не установлены, и / или многосимвольный параметр используется в проектах и применяется последовательно.
Не забывайте также соблюдать согласованность между сборками «Release» и «Debug».
источник
Очистить и восстановить
«Чистая» сборка может удалить «мертвую древесину», которая может быть оставлена на месте из предыдущих сборок, неудачных сборок, неполных сборок и других проблем сборки, связанных с системой сборки.
В общем случае IDE или сборка будут включать в себя некоторую форму «чистой» функции, но это может быть неправильно настроено (например, в файле ручной сборки) или может завершиться ошибкой (например, промежуточные или результирующие двоичные файлы доступны только для чтения).
После завершения «clean» убедитесь, что «clean» завершился успешно и все сгенерированные промежуточные файлы (например, автоматический make-файл) были успешно удалены.
Этот процесс можно рассматривать как последнее средство, но часто это хороший первый шаг ; особенно если недавно был добавлен код, связанный с ошибкой (локально или из исходного репозитория).
источник
Отсутствует "extern" в
const
объявлениях / определениях переменных (только C ++)Для людей, пришедших из C, может быть удивительным, что в C ++ глобальные
const
переменные имеют внутреннюю (или статическую) связь. В Си это было не так, поскольку все глобальные переменные неявноextern
(то есть, когдаstatic
ключевое слово отсутствует).Пример:
правильно будет использовать заголовочный файл и включить его в file2.cpp и file1.cpp
В качестве альтернативы можно объявить
const
переменную в file1.cpp с явнымextern
источник
Несмотря на то, что это довольно старые вопросы с несколькими принятыми ответами, я хотел бы поделиться тем, как устранить скрытую ошибку «неопределенная ссылка на».
Разные версии библиотек
Я использовал псевдоним для ссылки
std::filesystem::path
: файловая система находится в стандартной библиотеке начиная с C ++ 17, но моя программа должна была также компилироваться в C ++ 14, поэтому я решил использовать переменный псевдоним:Допустим, у меня есть три файла: 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")))
являются внешними в результирующем общем объекте.Вы можете проверить, являются ли символы, которые вы ищете, внешними, вызвав:
скрытые / локальные символы
nm
обозначаются строчными символами, напримерt
вместо `T для секции кода:Вы также можете использовать
nm
с опцией,-C
чтобы разобрать имена (если использовался C ++).Как и в Windows-DLL, можно пометить публичные функции с помощью определения, например,
DLL_PUBLIC
определенного как:Что примерно соответствует Windows / MSVC-версии:
Более подробную информацию о видимости можно найти в gcc wiki.
Когда модуль перевода скомпилирован с
-fvisibility=hidden
результирующими символами, они все еще имеют внешнюю связь (показывается символом верхнего регистра в виде символаnm
) и могут без проблем использоваться для внешней связи, если объектные файлы становятся частью статических библиотек. Связь становится локальной, только когда объектные файлы связаны в общей библиотеке.Чтобы определить, какие символы в объектном файле скрыты, выполните:
источник
nm -CD
илиnm -gCD
для просмотра внешних символов. Также см. Видимость на вики GCC.Разные архитектуры
Вы можете увидеть сообщение как:
В этом случае это означает, что доступные символы для другой архитектуры, чем та, для которой вы компилируете.
В Visual Studio это происходит из-за неправильной «Платформы», и вам нужно либо выбрать подходящую, либо установить правильную версию библиотеки.
В Linux это может быть связано с неправильной папкой библиотеки (используется
lib
вместо,lib64
например).В MacOS есть возможность отправить обе архитектуры в одном файле. Может быть так, что ссылка ожидает, что обе версии будут там, но только одна есть. Это также может быть проблема с неправильной папкой
lib
/, вlib64
которой находится библиотека.источник