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

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

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

29 ответов


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

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

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

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

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

скажем, вы определили символ 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 методы без определения, с добавленным рассуждением, что объявление pure генерирует фиктивную таблицу vtable, и вы можете получить ошибку компоновщика без использования функции:

struct X
{
    virtual void foo();
};
struct Y : X
{
   void foo() {}
};
int main()
{
   Y y; //linker error although there was no call to X::foo
}

для этого, чтобы работать, объявить X::foo() чиста:

struct X
{
    virtual void foo() = 0;
};

неvirtual учеников

некоторые члены должны быть определены, даже если они не используются явно:

struct A
{ 
    ~A();
};

следующее дало бы ошибку:

A a;      //destructor undefined

в реализация может быть встроенной, в самом определении класса:

struct A
{ 
    ~A() {}
};

или снаружи:

A::~A() {}

если реализация находится вне определения класса, но в заголовке, методы должны быть отмечены как inline для предотвращения множественного определения.

все используемые методы должны быть определены, если использовать.

распространенной ошибкой является забывание квалифицировать имя:

struct A
{
   void foo();
};

void foo() {}

int main()
{
   A a;
   a.foo();
}

определение должно будь

void A::foo() {}

static данные-члены должны быть определены вне класса в Единая единица перевода:

struct X
{
    static int x;
};
int main()
{
    int x = X::x;
}
//int X::x; //uncomment this line to define X::x

инициализатор может быть предоставлен для static const член данных интегрального типа или типа перечисления в определении класса; однако для использования odr этого члена все равно потребуется определение области пространства имен, как описано выше. C++11 позволяет инициализировать внутри класса для всех static const данные-члены.


неспособность связать с соответствующими библиотеками / объектными файлами или скомпилировать файлы реализации

обычно каждая единица перевода создает объектный файл, содержащий определения символов, определенных в этой единице перевода. Чтобы использовать эти символы, вы должны связать их с этими объектными файлами.

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

g++ -o test objectFile1.o objectFile2.o -lLibraryName

на libraryName вот только голое имя библиотеки, без дополнений, специфичных для платформы. Так, например, файлы библиотеки Linux обычно называются libfoo.so но вы только напишете -lfoo. В Windows этот же файл может называться foo.lib, но вы бы использовали тот же аргумент. Возможно, вам придется добавить каталог, в котором эти файлы можно найти с помощью -L‹directory›. Не пишите пробел после -l или -L.

на в Xcode: Добавьте пути поиска заголовка пользователя - > добавьте путь поиска библиотеки - > перетащите фактическую ссылку библиотеки в папку проекта.

под MSVS, файлы, добавленные в проект, автоматически связывают свои объектные файлы и lib будет создан файл (в обычном использовании). Чтобы использовать символы в отдельном проекте, необходимо включить lib файлы в настройках проекта. Это делается в разделе компоновщика свойств проекта, в Input -> Additional Dependencies. (путь к lib файл должен быть добавлено в Linker -> General -> Additional Library Directories) при использовании сторонней библиотеки, которая предоставляется с lib файл, неспособность сделать это обычно приводит к ошибке.

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

в программировании Windows контрольный знак, что вы не связали необходимую библиотеку, заключается в том, что имя неразрешенного символа начинается с __imp_. Искать имя функции в документации, и она должна сказать, какие библиотеки нужно использовать. Например, MSDN помещает информацию в поле внизу каждой функции в разделе "Библиотека".


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

типичное объявление переменных

extern int x;

поскольку это только декларация, a определение это. Соответствующее определение будет:

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

Compile:

$ 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 вот!--21-->

// 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

OK (без ошибок), потому что мы только строим объектный файл, связывание еще не сделано. Попробуйте связать

$ 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 нет метки, поэтому linker ее не нашел. Но мы-хакеры :) и мы можем это исправить. Открыть параметре src1.s в текстовом редакторе и изменить

.local  _ZL14local_var_name
.comm   _ZL14local_var_name,4,4

to

    .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

ok, см. Вывод readelf (символы)

$ readelf --symbols src1.o
8: 0000000000000000     4 OBJECT  GLOBAL DEFAULT    3 local_var_name

теперь local_var_name имеет привязку GLOBAL (был локальным)

ссылке

$ 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. Не существуют для начала. При попытке компиляции, ссылка шаг потерпит неудачу, потому что он не может найти B. lib. A. lib будет сгенерирован, но нет dll. Затем вы компилируете B, который будет успешным и создаст B. lib. Повторная компиляция a теперь будет работать, потому что B. lib теперь найден.


неправильно импортирует / экспортирует методы / классы через модули / 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
{
};

реализации шаблонов не отображаются.

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

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++, каждый исходный файл (.СРР.,C и т. д.) рассматривается как единица перевода, компилятор компилирует по одной единице за раз и генерирует один объектный файл(.obj) для текущей единицы перевода. (Обратите внимание, что каждый заголовочный файл, который включен в этот исходный файл, будет предварительно обработан и будет рассматриваться как часть этой единицы перевода)все в единице перевода считается внутренним, все остальное считается внешним. В C++ вы можете ссылаться на внешний символ используя ключевые слова, такие как extern, __declspec (dllimport) и так далее.

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

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

этот процесс поиска называется resolve.

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

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

  1. определения

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

int foo()
{
    return 0;
}

In 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 поскольку определение не существует в текущей области поиска.


неопределенная ссылка на WinMain@16 или подобное 'необычные' main() ссылка на точку входа (специально для visual-studio).

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

Если ваша IDE поддерживает Простые Консольные Проекты возможно, вы захотите выбрать этот тип проекта вместо проекта приложения windows.


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


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


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

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

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


пакет Visual Studio NuGet необходимо обновить для новой версии набора инструментов

у меня просто была эта проблема, пытаясь связать libpng с Visual Studio 2013. Проблема в том, что файл пакета были только библиотеки для Visual Studio 2010 и 2012.

правильное решение-надеяться, что разработчик выпустит обновленный пакет, а затем обновит его, но он работал для меня, взломав дополнительную настройку для VS2013, указав на библиотеку VS2012 файлы.

я отредактировал пакет (в packages папка внутри каталога решения), найдя packagename\build\native\packagename.targets и внутри этого файла, скопировав все v110 разделы. Я изменил v110 to v120 на только поля условия быть очень осторожным, чтобы оставить пути к имени файла все как v110. Это просто позволило Visual Studio 2013 ссылаться на библиотеки за 2012 год, и в этом случае это сработало.


Предположим, у вас есть большой проект, написанный на C++, который имеет тысячи .cpp файлы и тысячи .H-файлы.И, скажем, проект также зависит от десяти статических библиотек. Предположим, мы находимся в Windows и строим наш проект в Visual Studio 20xx. Когда вы нажимаете Ctrl + F7 Visual Studio, чтобы начать компиляцию всего решения (предположим, у нас есть только один проект в решении )

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

  • Visual Studio поиск в файле .расширением vcxproj и начать компиляцию каждого файла, который имеет расширение .СРР. Порядок компиляции не определен.Поэтому вы не должны предполагать, что файл main.cpp компилируется первым
  • если .cpp файлы зависят от дополнительных .H файлы для поиска символов это может быть или не быть определено в файле .cpp
  • если он существует .cpp-файл, в котором компилятор не смог найти один символ, a время compiler ошибка возникает сообщение символ x не найден
  • для каждого файла с расширением .cpp генерируется объектный файл .o а также Visual Studio записывает выходные данные в файл с именем имя проекта.СРР.Чистый.txt который содержит все объектные файлы, которые должны обрабатываться компоновщиком.

второй шаг компиляции выполняется компоновщиком.Компоновщик должен объединить весь объектный файл и построить, наконец, вывод (который может быть исполняемым или библиотека)

шаги по связыванию проекта

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

наблюдение

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

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

Компилятор Ошибка Времени :

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

Компоновщик Ошибка Времени

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

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

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

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

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

связаны .файл lib связан с a .dll файлы

У меня была та же проблема. Скажем, у меня есть проекты MyProject и TestProject. Я эффективно связал файл lib для MyProject с TestProject. Однако этот файл lib был создан как DLL для MyProject был построен. Кроме того, я не содержал исходный код для всех методов в MyProject, а только доступ к точкам входа DLL.

чтобы решить проблему, я построил MyProject как LIB и связал TestProject к этому .lib файл (я копирую вставить сгенерированный .lib-файл в папку TestProject). Затем я могу снова создать MyProject как DLL. Он компилируется, поскольку lib, с которым связан TestProject, содержит код для всех методов в классах в MyProject.


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

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

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

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

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


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

одна из возможных причин ошибок компоновщика с GCC 5.2.0 заключается в том, что по умолчанию выбрана новая библиотека libstdc++ ABI.

Если вы получаете ошибки компоновщика о неопределенных ссылках на символы, которые включают типы в пространстве имен std::__cxx11 или теге [abi: cxx11], то это, вероятно, указывает на то, что вы пытаетесь связать объектные файлы, которые были скомпилированы с различными значениями для макроса _GLIBCXX_USE_CXX11_ABI. Это обычно происходит при подключении к сторонней библиотеке, которая была скомпилирована с более старой версией GCC. Если сторонняя библиотека не может быть перестроена с новым ABI, вам нужно будет перекомпилировать свой код со старым ABI.

поэтому, если вы вдруг получите ошибки компоновщика при переключении на GCC после 5.1.0, это будет вещь, чтобы проверить.


оболочка вокруг GNU ld, которая не поддерживает скрипты компоновщика

некоторые .таким образом, файлы на самом деле скрипты компоновщика GNU ld, например,libtbb.так файл представляет собой текстовый ASCII-файл с таким содержанием:

INPUT (libtbb.so.2)

некоторые более сложные сборки могут не поддерживать эту. Например, если вы включаете -v в параметры компилятора, вы можете увидеть, что mainwin GCC обертка mwdip отбрасывает файлы команд сценария компоновщика в verbose выходной список библиотек для ссылки. Простая работа-заменить входной командный файл сценария компоновщика копией файла (или символической ссылкой), например

cp libtbb.so.2 libtbb.so

или вы можете заменить аргумент-l полным путем .так, например, вместо -ltbb do /home/foo/tbb-4.3/linux/lib/intel64/gcc4.4/libtbb.so.2


шаблоны дружбу...

учитывая фрагмент кода типа шаблона с оператором friend (или функцией);

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;

имя, впервые объявленное в объявлении friend в классе или шаблоне класса 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)


непоследовательны UNICODE определения

сборка Windows UNICODE построена с помощью TCHAR etc. определяется как wchar_t etc. Когда не здание с UNICODE определяется как сборка с TCHAR определяется как char etc. Эти UNICODE и _UNICODE определяет влияет на все "T" типы строк; LPTSTR, LPCTSTR и их лось.

создание одной библиотеки с UNICODE определен и пытается связать его в проекте, где UNICODE не определен приведет к ошибкам компоновщика, так как будет несоответствие в определении TCHAR; char и wchar_t.

ошибка обычно включает в себя функцию значение с char или wchar_t производный тип, они могут включать std::basic_string<> etc. также. При просмотре затрагиваемой функции в коде часто будет ссылка на TCHAR или std::basic_string<TCHAR> etc. Это признак того, что код изначально предназначался как для Юникода, так и для Многобайтового символа (или "узкое") строение.

чтобы исправить это, создайте все необходимые библиотеки и проекты с согласованным определением UNICODE_UNICODE).

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

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

    Свойства Проекта > Общие > Параметры Проекта > Набор Символов

  3. или по команде линия;

    /DUNICODE /D_UNICODE
    

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

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


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

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

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

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

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


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

  • вы пытаетесь скомпилировать и связать свою программу с цепочкой инструментов GCC.
  • ваша связь указывает все необходимые библиотеки и пути поиска библиотеки
  • если libfoo зависит от libbar, тогда ваша связь правильно ставит libfoo до libbar.
  • ваша связь терпит неудачу с undefined reference to что-то ошибки.
  • но все неопределено что-тоs объявлены в заголовочных файлах, которые у вас есть #included и фактически определены в библиотеках, которые вы связываете.

примеры находятся в C. Они также могут быть C++

минимальный пример с участием статической Библиотеке вы построили себя!--113-->

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

A минимальный пример, включающий общую системную библиотеку, библиотеку сжатия 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 are разрешить.

при попытке связать программу, как:

$ 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

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


когда ваши пути включения отличаются

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

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

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

// header1.h
typedef int Number;
void foo(Number);

// header2.h
typedef float Number;
void foo(Number); // this only looks the same lexically

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

вы можете спросить, как человек попадает в такую ситуацию? включаю пути конечно! Если при компиляции общей библиотеки путь include приводит к 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(...);
}

бум! Вы получаете ошибку компоновщика, и вы понятия не имеете, почему она терпит неудачу. Причина в том, что общая библиотека использует разные версии одного и того же include common_math.h (Я сделал это очевидным здесь, в Примере, включив другой путь, но это не всегда может быть так очевидно. Возможно, путь include отличается в компиляторе настроить.)

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

отладка линкер

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

процесс идет следующим образом:

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

[1] под проектом я подразумеваю набор исходных файлов, связанных вместе для создания библиотеки или исполняемого файла.

EDIT 1: переписать первый раздел, чтобы его было легче понять. Пожалуйста, прокомментируйте ниже, чтобы сообщить мне, если что-то еще нужно исправить. Спасибо!


отсутствует "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 и файл1.cpp

extern const int test;
extern int test2;

в качестве альтернативы можно объявить const переменная в file1.cpp с явным extern


хотя это довольно старые вопросы с несколькими принятыми ответами, я хотел бы поделиться тем, как решить непонятных" неопределенная ссылка на " Ошибка.

различные версии библиотек

я использовал псевдоним для ссылки на std::filesystem::path: файловая система находится в стандартной библиотеке с C++17, но моей программе необходимо также компилируется в C++14 поэтому я решил использовать псевдоним переменной:

#if (defined _GLIBCXX_EXPERIMENTAL_FILESYSTEM) //is the included filesystem library experimental? (C++14 and C++17: <experimental/filesystem>)
using path_t = std::experimental::filesystem::path;
#elif (defined _GLIBCXX_FILESYSTEM) //not experimental: C++17 <filesystem>
using path_t = std::filesystem::path;
#endif
давай у меня три файлы: main.СРР, файл.h, файл.cpp:
  • .h #include в experimental:: filestystem> и содержит код выше
  • .cpp реализация файл.h, #include ".h"
  • main.cpp #include в filestystem> и ".h"

Примечание разные библиотеки используется в main.СРР и файл.ч. Так как основным.ЧГК #включить что ".h" после filestystem>, версия используемой файловой системы была C++17 one. Я использовал для компиляции программы следующие команды:

$ g++ -g -std=c++17 -c main.cpp - > компилирует main.cpp-main.o
$ g++ -g -std=c++17 -c file.cpp -> компилирует файл.cpp и файл.H в файл.o
$ g++ -g -std=c++17 -o executable main.o file.o -lstdc++fs -> ссылки main.O и файл.o

таким образом функции содержащиеся в файле.O и используется в главный.О, это!--9-->требуются path_t дал ошибки "неопределенная ссылка", потому что main.o называют std::filesystem::path но .o to std::experimental::filesystem::path.

разрешение

чтобы исправить это, мне просто нужно было изменить в файл.ч .


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

поведение по умолчанию 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 

отображаются скрытые / локальные символы by nm С типом символа нижнего регистра, например t вместо ' T для раздела кода:

nm XXX.so
00000000000005a7 t HIDDEN_SYMBOL
00000000000005f8 T VISIBLE_SYMBOL

вы также можете использовать nm с параметром -C в demangle имена (если c++ был использован).

подобно Windows-DLL, можно было бы отметить публичные функции с помощью define, например DLL_PUBLIC определено как:

#define DLL_PUBLIC __attribute__ ((visibility ("default")))

DLL_PUBLIC int my_public_function(){
  ...
}

что примерно соответствует Windows ' / MSVC-version:

#ifdef BUILDING_DLL
    #define DLL_PUBLIC __declspec(dllexport) 
#else
    #define DLL_PUBLIC __declspec(dllimport) 
#endif

больше информация о видимость можно найти на вики gcc.


когда блок перевода компилируется с -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