Что такое неопределенная ссылка / неразрешенная внешняя ошибка символа и как ее исправить?

Что такое неопределенные ссылки / неразрешенные ошибки внешних символов? Каковы общие причины и как их исправить / предотвратить?

Не стесняйтесь редактировать / добавлять свои собственные.

41 ответ

Решение

Компиляция программы на C++ происходит в несколько этапов, как указано в 2.2 (благодарность Кита Томпсону за справку):

Приоритет среди синтаксических правил перевода определяется следующими этапами [см. Сноску].

  1. Физические символы исходного файла отображаются, в зависимости от реализации, в базовый исходный набор символов (ввод символов новой строки для индикаторов конца строки), если это необходимо. [СНиП]
  2. Каждый экземпляр символа обратной косой черты (\), за которым сразу следует символ новой строки, удаляется, объединяя физические исходные строки для формирования логических исходных строк. [СНиП]
  3. Исходный файл разбит на токены предварительной обработки (2.5) и последовательности символов пробела (включая комментарии). [СНиП]
  4. Выполняются директивы предварительной обработки, расширяются вызовы макросов и выполняются выражения унарного оператора _Pragma. [СНиП]
  5. Каждый элемент исходного набора символов в символьном литерале или строковом литерале, а также каждая escape-последовательность и универсальное имя-символа в символьном литерале или неочищенном строковом литерале преобразуется в соответствующий элемент набора исполняемых символов; [СНиП]
  6. Литеральные токены соседних строк объединяются.
  7. Пробелы, разделяющие токены, больше не имеют значения. Каждый токен предварительной обработки преобразуется в токен. (2.7). Полученные токены синтаксически и семантически анализируются и переводятся как единица перевода. [СНиП]
  8. Переведенные единицы перевода и единицы реализации объединяются следующим образом: [SNIP]
  9. Все ссылки на внешние объекты разрешены. Компоненты библиотеки связаны для удовлетворения внешних ссылок на объекты, не определенные в текущем переводе. Весь такой вывод транслятора собирается в образ программы, который содержит информацию, необходимую для выполнения в среде выполнения. (акцент мой)

[сноска] Реализации должны вести себя так, как будто эти отдельные фазы происходят, хотя на практике разные фазы могут складываться вместе.

Указанные ошибки возникают на этом последнем этапе компиляции, который чаще всего называют связыванием. По сути, это означает, что вы скомпилировали кучу файлов реализации в объектные файлы или библиотеки и теперь хотите, чтобы они работали вместе.

Скажем, вы определили символ 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

Общие причины включают в себя:

Члены класса:

Чистый 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

Инициализатор может быть предоставлен для staticconst член данных целочисленного или перечислимого типа в определении класса; однако для использования 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 помещает информацию в поле внизу каждой функции в разделе "Библиотека".

Объявлен, но не определил переменную или функцию.

Типичное объявление переменной

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)

Другие примеры несоответствий включают

  • Функция / переменная объявлена ​​в одном пространстве имен, определена в другом.
  • Функция / переменная объявлена ​​как член класса, определена как глобальная (или наоборот).
  • Тип возвращаемого значения функции, номер параметра и типы, а также соглашение о вызовах не полностью совпадают.

Сообщение об ошибке от компилятора часто дает вам полное объявление переменной или функции, которая была объявлена, но никогда не определялась. Сравните это с приведенным вами определением. Убедитесь, что каждая деталь соответствует.

Порядок, в котором указываются взаимозависимые связанные библиотеки, неверен.

Порядок, в котором связаны библиотеки, имеет значение, если библиотеки зависят друг от друга. В общем, если библиотека 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

хорошо, мы взломали это:)

Таким образом, в результате - "неопределенная ссылка / неразрешенная ошибка внешнего символа" происходит, когда компоновщик не может найти глобальные символы в объектных файлах.

Символы были определены в программе на C и использованы в коде C++.

Функция (или переменная) 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"
}

Если ничего не помогает, перекомпилируйте.

Недавно я смог избавиться от нерешенной внешней ошибки в Visual Studio 2012, просто перекомпилировав файл-нарушитель. Когда я перестроил, ошибка ушла.

Это обычно происходит, когда две (или более) библиотеки имеют циклическую зависимость. Библиотека A пытается использовать символы из B.lib, а библиотека B пытается использовать символы из A.lib. Ни один не существует, чтобы начать с. Когда вы попытаетесь скомпилировать A, шаг ссылки потерпит неудачу, потому что он не может найти B.lib. A.lib будет сгенерирован, но не dll. Затем вы компилируете B, который преуспеет и сгенерирует B.lib. Перекомпиляция A теперь будет работать, потому что B.lib теперь найден.

Реализации шаблона не видны.

Неспециализированные шаблоны должны иметь свои определения, видимые для всех единиц перевода, которые их используют. Это означает, что вы не можете отделить определение шаблона от файла реализации. Если вы должны отделить реализацию, обычный обходной путь должен иметь 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 в файл заголовка или в другое место, видимое для модуля перевода, который его использует.

Специализированные шаблоны могут быть реализованы в файле реализации, и реализация не должна быть видимой, но специализация должна быть предварительно объявлена.

Для дальнейшего объяснения и другого возможного решения (явной реализации) см. Этот вопрос и ответ.

Это одно из самых запутанных сообщений об ошибках, которые каждый программист VC++ видел снова и снова. Давайте сначала проясним ситуацию.

А. Что такое символ? Короче говоря, символ - это имя. Это может быть имя переменной, имя функции, имя класса, имя typedef или что угодно, кроме тех имен и знаков, которые принадлежат языку C++. Он определяется пользователем или вводится библиотекой зависимостей (еще одна определяется пользователем).

B. Что является внешним? В VC++ каждый исходный файл (.cpp,.c и т. Д.) Рассматривается как модуль перевода, компилятор компилирует один модуль за раз и генерирует один объектный файл (.obj) для текущего модуля перевода. (Обратите внимание, что каждый заголовочный файл, включенный в этот исходный файл, будет предварительно обработан и будет считаться частью этого модуля перевода) Все в модуле перевода рассматривается как внутреннее, все остальное рассматривается как внешнее. В C++ вы можете ссылаться на внешний символ, используя такие ключевые слова, как extern, __declspec (dllimport) и так далее.

C. Что такое "решимость"? Resolve - это термин времени связывания. Во время компоновки компоновщик пытается найти внешнее определение для каждого символа в объектных файлах, которые не могут найти его определение внутри. Объем этого процесса поиска, включая:

  • Все объектные файлы, созданные во время компиляции
  • Все библиотеки (.lib), которые явно или неявно указаны как дополнительные зависимости этого строительного приложения.

Этот процесс поиска называется решимостью.

D. Наконец, почему неразрешенный внешний символ? Если компоновщик не может найти внешнее определение для символа, который не имеет внутреннего определения, он сообщает об ошибке "Неразрешенный внешний символ".

E. Возможные причины LNK2019: неразрешенная ошибка внешнего символа. Мы уже знаем, что эта ошибка связана с тем, что компоновщику не удалось найти определение внешних символов, возможные причины можно отсортировать по следующим параметрам:

  1. Определение существует

Например, если у нас есть функция с именем foo, определенная в a.cpp:

int foo()
{
    return 0;
}

В b.cpp мы хотим вызвать функцию foo, поэтому добавим

void foo();

объявить функцию foo() и вызвать ее в другом теле функции, скажем bar():

void bar()
{
    foo();
}

Теперь, когда вы создаете этот код, вы получите ошибку LNK2019 с жалобой на то, что foo является неразрешенным символом. В этом случае мы знаем, что foo() имеет свое определение в a.cpp, но отличается от того, который мы вызываем (другое возвращаемое значение). Это тот случай, когда определение существует.

  1. Определение не существует

Если мы хотим вызвать некоторые функции в библиотеке, но библиотека импорта не добавляется в дополнительный список зависимостей (устанавливается из: Project | Properties | Configuration Properties | Linker | Input | Additional Dependency) вашего проекта. Теперь компоновщик сообщит LNK2019, так как определение не существует в текущей области поиска.

Неправильный импорт / экспорт методов / классов через модули /dll (зависит от компилятора).

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
{
};

неопределенная ссылка на WinMain@16 или похожий "необычный" main() ссылка на точку входа (особенно для /questions/tagged/visual-studio).

Возможно, вы пропустили выбор правильного типа проекта с вашей фактической IDE. В среде IDE может потребоваться привязать, например, проекты приложений Windows к такой функции точки входа (как указано в отсутствующей ссылке выше), вместо обычно используемой int main(int argc, char** argv); подпись.

Если ваша IDE поддерживает простые консольные проекты, вы можете выбрать этот тип проекта вместо проекта Windows.


Здесь case1 и case2 рассматриваются более подробно из реальной проблемы.

Также, если вы используете сторонние библиотеки, убедитесь, что у вас есть правильные 32/64-битные двоичные файлы

Пакет 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, чтобы начать компиляцию всего решения (предположим, у нас есть только один проект в решении)

В чем смысл компиляции?

  • Visual Studio ищет файл .vcxproj и начинает компилировать каждый файл с расширением.cpp. Порядок компиляции не определен. Так что вы не должны предполагать, что файл main.cpp компилируется первым
  • Если файлы.cpp зависят от дополнительных файлов.h, чтобы найти символы, которые могут или не могут быть определены в файле.cpp
  • Если существует один файл.cpp, в котором компилятор не может найти один символ, ошибка времени компилятора вызывает сообщение Символ x не найден
  • Для каждого файла с расширением.cpp создается объектный файл.o, а также Visual Studio записывает выходные данные в файл с именем ProjectName.Cpp.Clean.txt, который содержит все объектные файлы, которые должны обрабатываться компоновщиком.

Второй этап компиляции выполняется Linker.Linker должен объединить весь объектный файл и, наконец, создать вывод (который может быть исполняемым файлом или библиотекой).

Шаги в Связывание проекта

  • Проанализируйте все объектные файлы и найдите определение, которое было объявлено только в заголовках (например: код одного метода класса, как упомянуто в предыдущих ответах, или событие инициализации статической переменной, которая является членом внутри класса)
  • Если один символ не может быть найден в объектных файлах, его также ищут в дополнительных библиотеках. Для добавления новой библиотеки в свойства конфигурации проекта -> Каталоги VC++ -> Каталоги библиотек, и здесь вы указали дополнительную папку для поиска библиотек и Свойства конфигурации -> Линкер -> Вход для указания имени библиотеки. -Если компоновщик не может найти символ, который вы пишете в одном.cpp, он вызывает ошибку компоновщика, которая может звучать как error LNK2001: unresolved external symbol "void __cdecl foo(void)" (?foo@@YAXXZ)

наблюдение

  1. Как только Линкер найдет один символ, он не будет искать его в других библиотеках
  2. Порядок связывания библиотек имеет значение.
  3. Если компоновщик находит внешний символ в одной статической библиотеке, он включает этот символ в выходные данные проекта. Однако, если библиотека является общей (динамической), он не включает код (символы) в выходные данные, но сбой во время выполнения может происходить

Как решить эту ошибку

Ошибка времени компиляции:

  • Убедитесь, что вы написали свой синтаксический проект C++ правильно.

Ошибка компоновщика

  • Определите все ваши символы, которые вы объявляете в заголовочных файлах
  • использование #pragma once за разрешение компилятору не включать один заголовок, если он уже был включен в текущий.cpp, который компилируется
  • Убедитесь, что ваша внешняя библиотека не содержит символов, которые могут вступать в конфликт с другими символами, которые вы определили в ваших заголовочных файлах.
  • Когда вы используете шаблон, чтобы убедиться, что вы включили определение каждой функции шаблона в файл заголовка, чтобы позволить компилятору генерировать соответствующий код для любых экземпляров.

Microsoft предлагает #pragma ссылаться на правильную библиотеку во время ссылки;

#pragma comment(lib, "libname.lib")

В дополнение к пути к библиотеке, включая каталог библиотеки, это должно быть полное имя библиотеки.

Используйте компоновщик, чтобы помочь диагностировать ошибку

Большинство современных компоновщиков содержат подробный вариант, который печатает в разной степени;

  • Ссылочный вызов (командная строка),
  • Данные о том, какие библиотеки включены в этап ссылки,
  • Расположение библиотек,
  • Используемые пути поиска.

Для gcc и clang; вы обычно добавляете -v -Wl,--verbose или же -v -Wl,-v в командной строке. Более подробную информацию можно найти здесь;

Для MSVC, /VERBOSE (особенно /VERBOSE:LIB) добавляется в командную строку ссылки.

Ошибка в компиляторе /IDE

У меня недавно была эта проблема, и оказалось, что это ошибка в Visual Studio Express 2013. Мне пришлось удалить исходный файл из проекта и повторно добавить его, чтобы устранить ошибку.

Действия, если вы считаете, что это может быть ошибка в компиляторе /IDE:

  • Очистите проект (некоторые IDE имеют возможность сделать это, вы также можете сделать это вручную, удалив объектные файлы)
  • Попробуйте начать новый проект, скопировав весь исходный код из исходного.

Связанный.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, это было бы полезно проверить.

Ваша связь использует библиотеки перед объектными файлами, которые ссылаются на них

  • Вы пытаетесь скомпилировать и связать свою программу с помощью цепочки инструментов GCC.
  • Ваша связь указывает все необходимые библиотеки и пути поиска библиотек
  • Если 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

Ссылка на пример 2 pkg-config Вариация правильно:

$ 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 снова.

Я могу воспроизвести проблему в примере 1, но не в примере 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':

Смотрите также

Порядок, в котором указаны взаимозависимые связанные библиотеки, неверен

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

Обертка вокруг 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 делать /home/foo/tbb-4.3/linux/lib/intel64/gcc4.4/libtbb.so.2

Шаблоны для друзей...

Дан фрагмент кода типа шаблона с оператором друга (или функцией);

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! Вы получаете ошибку компоновщика, и вы не знаете, почему он не работает. Причина в том, что общая библиотека использует разные версии одного и того же common_math.h (Я сделал это очевидным здесь, в примере, включив другой путь, но это не всегда может быть так очевидно. Возможно, путь включения отличается в настройках компилятора).

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

Отладка компоновщика

DUMPBIN - ваш друг, если вы используете Visual Studio. Я уверен, что другие компиляторы имеют другие подобные инструменты.

Процесс идет так:

  1. Обратите внимание на странное искаженное имя, данное в ошибке компоновщика. (например, draw@graphics@XYZ).
  2. Сохраните экспортированные символы из библиотеки в текстовый файл.
  3. Найдите экспортированный символ интереса и обратите внимание, что искаженное имя отличается.
  4. Обратите внимание на то, почему искаженные имена оказались разными. Вы сможете увидеть, что типы параметров различаются, даже если они выглядят одинаково в исходном коде.
  5. Причина, почему они разные. В приведенном выше примере они отличаются из-за разных включаемых файлов.

[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).

  1. Это может быть сделано с любой;

    #define UNICODE
    #define _UNICODE
    
  2. Или в настройках проекта;

    Свойства проекта> Общие> Проект по умолчанию> Набор символов

  3. Или в командной строке;

    /DUNICODE /D_UNICODE
    

Альтернатива также применима, если UNICODE не предназначен для использования, убедитесь, что определения не установлены, и / или многосимвольный параметр используется в проектах и ​​применяется последовательно.

Не забывайте также соблюдать согласованность между сборками "Release" и "Debug".

Очистить и восстановить

"Чистая" сборка может удалить "мертвую древесину", которая может быть оставлена ​​без присмотра от предыдущих сборок, неудачных сборок, неполных сборок и других проблем, связанных со сборкой системы сборки.

В общем случае IDE или сборка будут включать в себя некоторую форму "чистой" функции, но она может быть неправильно настроена (например, в файле ручной сборки) или может не работать (например, промежуточные или результирующие двоичные файлы доступны только для чтения).

После завершения "очистки" убедитесь, что "очистка" прошла успешно и все сгенерированные промежуточные файлы (например, автоматический make-файл) были успешно удалены.

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

Отсутствует "extern" в const объявления / определения переменных (только C++)

Для людей, пришедших из C, может быть сюрпризом, что в C++ global constпеременные имеют внутреннюю (или статическую) связь. В C это было не так, поскольку все глобальные переменные неявно 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: filesystem находится в стандартной библиотеке начиная с C++17, но моя программа должна была также компилироваться в C++14, поэтому я решил использовать переменную alias:

#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:

  • file.h # include < экспериментальная:: файловая система > и содержит код выше
  • file.cpp, реализация file.h, #include's file.h
  • main.cpp # include < filestystem > и " file.h "

Обратите внимание на различные библиотеки, используемые в main.cpp и file.h. Поскольку main.cpp #include'd " file.h " после < filestystem >, используемой версией файловой системы была версия 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 но файл.о к std::experimental::filesystem::path,

разрешение

Чтобы это исправить, мне просто нужно было изменить <Экспериментальный:: filestystem> в file.h на .

Ошибка "Неопределенная ссылка" возникает, когда у нас есть ссылка на имя объекта (класс, функция, переменная и т. Д.) В нашей программе, и компоновщик не может найти его определение, когда он пытается найти его во всех связанных объектных файлах и библиотеках..

Таким образом, когда компоновщик не может найти определение связанного объекта, он выдает ошибку "неопределенная ссылка". Как видно из определения, эта ошибка возникает на более поздних этапах процесса связывания. Есть разные причины, которые вызывают ошибку "неопределенная ссылка".

Возможная причина (чаще):

#1) Для объекта нет определения

Это простейшая причина возникновения ошибки "неопределенная ссылка". Программист просто забыл определить объект.

Рассмотрим следующую программу на C++. Здесь мы только указали прототип функции, а затем использовали его в основной функции.

#include <iostream>
int func1();
int main()
{
     
    func1();
}

Вывод:

main.cpp:(.text+0x5): undefined reference to 'func1()'
collect2: error ld returned 1 exit status

Поэтому, когда мы компилируем эту программу, выдается сообщение об ошибке компоновщика "неопределенная ссылка на 'func1()'".

Чтобы избавиться от этой ошибки, мы исправляем программу следующим образом, предоставляя определение функции func1. Теперь программа выдает соответствующий результат.

#include <iostream>
using namespace std;
int func1();
 
int main()
{
     
    func1();
}
int func1(){
    cout<<"hello, world!!";
}

Вывод:

hello, world!!

#2) Неправильное определение (подписи не совпадают) используемых объектов

Еще одна причина ошибки "неопределенная ссылка" - это неправильные определения. Мы используем любой объект в нашей программе, и его определение отличается.

Рассмотрим следующую программу на C++. Здесь мы сделали вызов func1 (). Его прототип - int func1 (). Но его определение не соответствует его прототипу. Как мы видим, определение функции содержит параметр функции.

Таким образом, когда программа компилируется, компиляция проходит успешно из-за совпадения прототипа и вызова функции. Но когда компоновщик пытается связать вызов функции с ее определением, он находит проблему и выдает ошибку как "неопределенную ссылку".

#include <iostream>
using namespace std;
int func1();
int main()
{
     
    func1();
}
int func1(int n){
    cout<<"hello, world!!";
}

Вывод:

main.cpp:(.text+0x5): undefined reference to 'func1()'
collect2: error ld returned 1 exit status

Таким образом, чтобы предотвратить такие ошибки, мы просто проверяем, совпадают ли определения и использование всех объектов в нашей программе.

#3) объектные файлы не связаны должным образом

Эта проблема также может вызвать ошибку "неопределенная ссылка". Здесь у нас может быть несколько исходных файлов, и мы можем компилировать их независимо. Когда это будет сделано, объекты не будут правильно связаны, и это приведет к "неопределенной ссылке".

Рассмотрим следующие две программы на C++. В первом файле мы используем функцию "print ()", которая определена во втором файле. Когда мы компилируем эти файлы по отдельности, первый файл дает "неопределенную ссылку" для функции печати, а второй файл дает "неопределенную ссылку" для основной функции.

int print();
int main()
{
    print();
}

Вывод:

main.cpp:(.text+0x5): undefined reference to 'print()'
collect2: error ld returned 1 exit status

int print() {
    return 42;
}

Вывод:

(.text+0x20): undefined reference to 'main'
collect2: error ld returned 1 exit status

Способ устранения этой ошибки - одновременная компиляция обоих файлов (например, с помощью g++).

Помимо уже рассмотренных причин, "неопределенная ссылка" может также возникать по следующим причинам.

#4) Неправильный тип проекта

Когда мы указываем неправильные типы проектов в C++ IDE, таких как Visual Studio, и пытаемся делать то, чего проект не ожидает, мы получаем "неопределенную ссылку".

#5) Нет библиотеки

Если программист неправильно указал путь к библиотеке или полностью забыл указать его, то мы получаем "неопределенную ссылку" для всех ссылок, которые программа использует из библиотеки.

#6) Зависимые файлы не компилируются

Программист должен заранее убедиться, что мы скомпилировали все зависимости проекта, чтобы при компиляции проекта компилятор нашел все зависимости и успешно компилировал. Если какая-либо из зависимостей отсутствует, компилятор выдает "неопределенную ссылку".

Помимо причин, описанных выше, ошибка "неопределенная ссылка" может возникать во многих других ситуациях. Но суть в том, что программист ошибся, и чтобы предотвратить эту ошибку, их нужно исправить.

При связывании с общими библиотеками убедитесь, что используемые символы не скрыты.

Поведение 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
Другие вопросы по тегам