Co to jest niezdefiniowany odwołanie / nierozwiązany błąd zewnętrznego symbolu i jak to naprawić?

Sep 25 2012

Co to są niezdefiniowane odniesienia / nierozwiązane błędy symboli zewnętrznych? Jakie są najczęstsze przyczyny i jak im zapobiegać / im zapobiegać?

Zapraszam do edycji / dodawania własnych.

Odpowiedzi

880 LuchianGrigore Sep 25 2012 at 05:27

Kompilowanie programu w C ++ odbywa się w kilku etapach, jak określono w 2.2 (w celach informacyjnych kredyty dla Keitha Thompsona) :

Pierwszeństwo między regułami składni tłumaczenia określają następujące fazy [patrz przypis] .

  1. Fizyczne znaki pliku źródłowego są mapowane, w sposób zdefiniowany w implementacji, na podstawowy zestaw znaków źródłowych (wprowadzając znaki nowego wiersza dla wskaźników końca wiersza), jeśli jest to konieczne. [FANTASTYCZNA OKAZJA]
  2. Każde wystąpienie znaku ukośnika odwrotnego (\), po którym bezpośrednio następuje znak nowego wiersza, jest usuwane, łącząc fizyczne wiersze źródłowe w celu utworzenia logicznych wierszy źródłowych. [FANTASTYCZNA OKAZJA]
  3. Plik źródłowy jest dekomponowany na tokeny przetwarzania wstępnego (2.5) i sekwencje znaków odstępu (w tym komentarze). [FANTASTYCZNA OKAZJA]
  4. Dyrektywy przetwarzania wstępnego są wykonywane, wywołania makr są rozwijane i wykonywane są wyrażenia operatora jednoargumentowego _Pragma. [FANTASTYCZNA OKAZJA]
  5. Każdy element członkowski zestawu znaków źródłowych w literale znakowym lub literale ciągu, a także każda sekwencja zmiany znaczenia i nazwa uniwersalnego znaku w literale znakowym lub w innym niż surowym literale ciągu są konwertowane na odpowiadający mu element zestawu znaków wykonania; [FANTASTYCZNA OKAZJA]
  6. Sąsiednie tokeny literału ciągu są konkatenowane.
  7. Białe znaki oddzielające tokeny nie mają już znaczenia. Każdy token przetwarzania wstępnego jest konwertowany na token. (2,7). Powstałe tokeny są analizowane składniowo i semantycznie i tłumaczone jako jednostka translacyjna. [FANTASTYCZNA OKAZJA]
  8. Przetłumaczone jednostki tłumaczeniowe i jednostki instancji są połączone w następujący sposób: [SNIP]
  9. Wszystkie odniesienia do jednostek zewnętrznych są rozwiązywane. Komponenty biblioteki są połączone, aby spełnić zewnętrzne odniesienia do elementów niezdefiniowanych w bieżącym tłumaczeniu. Wszystkie takie dane wyjściowe translatora są gromadzone w obrazie programu, który zawiera informacje potrzebne do wykonania w jego środowisku wykonawczym. (podkreślenie moje)

[przypis] Wdrożenia muszą zachowywać się tak, jakby miały miejsce te oddzielne fazy, chociaż w praktyce różne fazy mogą być połączone.

Określone błędy występują na tym ostatnim etapie kompilacji, najczęściej nazywanym linkowaniem. Zasadniczo oznacza to, że skompilowałeś kilka plików implementacyjnych do plików obiektowych lub bibliotek, a teraz chcesz, aby współpracowały.

Powiedzmy, że zdefiniowałeś symbol aw a.cpp. Teraz b.cpp zadeklarowałem ten symbol i użyłem go. Przed połączeniem zakłada po prostu, że ten symbol został gdzieś zdefiniowany , ale nie obchodzi go jeszcze, gdzie. Faza łączenia jest odpowiedzialna za znalezienie symbolu i poprawne powiązanie go z b.cpp(cóż, właściwie z obiektem lub biblioteką, która go używa).

Jeśli używasz programu Microsoft Visual Studio, zobaczysz, że projekty generują .libpliki. Zawierają one tabelę wyeksportowanych symboli i tabelę importowanych symboli. Importowane symbole są porównywane z bibliotekami, z którymi się łączysz, a wyeksportowane symbole są dostarczane dla bibliotek, które ich używają .lib(jeśli takie istnieją).

Podobne mechanizmy istnieją dla innych kompilatorów / platform.

Typowe komunikaty o błędach są error LNK2001, error LNK1120, error LNK2019dla Microsoft Visual Studio i undefined reference to symbolName dla GCC .

Kod:

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

wygeneruje następujące błędy w 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

i podobne błędy w 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

Typowe przyczyny to:

  • Brak połączenia z odpowiednimi bibliotekami / plikami obiektów lub kompilacją plików implementacji
  • Zadeklarowana i niezdefiniowana zmienna lub funkcja.
  • Typowe problemy z członkami typu klasy
  • Implementacje szablonów nie są widoczne.
  • Symbole zostały zdefiniowane w programie C i użyte w kodzie C ++.
  • Nieprawidłowe importowanie / eksportowanie metod / klas między modułami / biblioteką dll. (Specyficzne dla MSVS)
  • Zależność biblioteki cyklicznej
  • niezdefiniowane odniesienie do `WinMain @ 16 '
  • Kolejność bibliotek współzależnych
  • Wiele plików źródłowych o tej samej nazwie
  • Błędne wpisywanie lub brak rozszerzenia .lib podczas korzystania z #pragma(Microsoft Visual Studio)
  • Problemy ze znajomymi z szablonu
  • Niespójne UNICODEdefinicje
  • Brak „extern” w deklaracjach / definicjach zmiennych const (tylko C ++)
185 LuchianGrigore Sep 25 2012 at 06:38

Członkowie klasy:

Czysty virtualdestruktor potrzebuje implementacji.

Deklarowanie destruktora w stanie czystym nadal wymaga zdefiniowania go (w przeciwieństwie do zwykłej funkcji):

struct X
{
    virtual ~X() = 0;
};
struct Y : X
{
    ~Y() {}
};
int main()
{
    Y y;
}
//X::~X(){} //uncomment this line for successful definition

Dzieje się tak, ponieważ destruktory klasy bazowej są wywoływane, gdy obiekt jest niejawnie niszczony, więc wymagana jest definicja.

virtual metody muszą być zaimplementowane lub zdefiniowane jako czyste.

Jest to podobne do virtualmetod niebędących metodami bez definicji, z dodatkowym rozumowaniem, że czysta deklaracja generuje fikcyjną tabelę v i możesz otrzymać błąd konsolidatora bez użycia funkcji:

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

Aby to zadziałało, zadeklaruj X::foo()jako czyste:

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

virtualCzłonkowie niebędący członkami klasy

Niektórych członków należy zdefiniować, nawet jeśli nie są używane jawnie:

struct A
{ 
    ~A();
};

Poniższy błąd spowodowałby błąd:

A a;      //destructor undefined

Implementacja może być wbudowana, w samej definicji klasy:

struct A
{ 
    ~A() {}
};

lub na zewnątrz:

A::~A() {}

Jeśli implementacja znajduje się poza definicją klasy, ale w nagłówku, metody muszą być oznaczone, inlineaby zapobiec wielokrotnej definicji.

Jeśli są używane, należy zdefiniować wszystkie używane metody składowe.

Częstym błędem jest zapominanie o zakwalifikowaniu nazwy:

struct A
{
   void foo();
};

void foo() {}

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

Definicja powinna być

void A::foo() {}

staticczłonkowie danych muszą być zdefiniowani poza klasą w jednej jednostce tłumaczeniowej :

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

W static constramach definicji klasy można podać inicjator dla elementu członkowskiego danych typu integralnego lub wyliczeniowego; jednak użycie odr tego elementu członkowskiego będzie nadal wymagało definicji zakresu przestrzeni nazw, jak opisano powyżej. C ++ 11 umożliwia inicjalizację wewnątrz klasy dla wszystkich static constczłonków danych.

121 LuchianGrigore Sep 25 2012 at 06:37

Brak połączenia z odpowiednimi bibliotekami / plikami obiektów lub kompilacją plików implementacji

Zwykle każda jednostka translacyjna generuje plik obiektowy, który zawiera definicje symboli zdefiniowanych w tej jednostce tłumaczeniowej. Aby użyć tych symboli, musisz połączyć się z tymi plikami obiektowymi.

W gcc możesz określić wszystkie pliki obiektowe, które mają być połączone razem w linii poleceń, lub skompilować razem pliki implementacyjne.

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

libraryNameTutaj jest tylko gołe nazwa biblioteki, bez platformy specyficznych dodatków. Na przykład w Linuksie pliki bibliotek są zwykle wywoływane, libfoo.soale można tylko pisać -lfoo. W systemie Windows ten sam plik może zostać wywołany foo.lib, ale użyjesz tego samego argumentu. Może być konieczne dodanie katalogu, w którym można znaleźć te pliki za pomocą -L‹directory›. Nie wpisuj spacji po -llub -L.

W przypadku XCode : dodaj ścieżki wyszukiwania nagłówków użytkownika -> dodaj ścieżkę wyszukiwania biblioteki -> przeciągnij i upuść rzeczywiste odniesienie do biblioteki do folderu projektu.

W ramach MSVS pliki dodane do projektu mają automatycznie połączone ze sobą pliki obiektowe i libplik zostanie wygenerowany (w powszechnym użyciu). Aby użyć symboli w oddzielnym projekcie, musisz uwzględnić libpliki w ustawieniach projektu. Odbywa się to w sekcji konsolidatora właściwości projektu w Input -> Additional Dependencies. (ścieżkę do libpliku należy dodać Linker -> General -> Additional Library Directories) W przypadku korzystania z biblioteki innej firmy, która jest dostarczana z libplikiem, niepowodzenie zwykle powoduje błąd.

Może się również zdarzyć, że zapomnisz dodać plik do kompilacji, w takim przypadku plik obiektowy nie zostanie wygenerowany. W gcc dodajesz pliki do wiersza poleceń. W MSVS dodanie pliku do projektu spowoduje jego automatyczną kompilację (chociaż pliki można ręcznie wykluczyć z kompilacji).

W programowaniu w systemie Windows znakiem ostrzegawczym, że nie utworzono połączenia z niezbędną biblioteką, jest to, że nazwa nierozwiązanego symbolu zaczyna się od __imp_. Sprawdź nazwę funkcji w dokumentacji i powinna wskazywać, której biblioteki potrzebujesz. Na przykład MSDN umieszcza informacje w polu u dołu każdej funkcji w sekcji o nazwie „Biblioteka”.

105 LuchianGrigore Sep 25 2012 at 06:38

Zadeklarowano, ale nie zdefiniowano zmiennej ani funkcji.

Typowa deklaracja zmiennej to

extern int x;

Ponieważ jest to tylko deklaracja, potrzebna jest jedna definicja . Odpowiednia definicja brzmiałaby:

int x;

Na przykład poniższe spowodowałoby błąd:

extern int x;
int main()
{
    x = 0;
}
//int x; // uncomment this line for successful definition

Podobne uwagi dotyczą funkcji. Zadeklarowanie funkcji bez jej zdefiniowania prowadzi do błędu:

void foo(); // declaration only
int main()
{
   foo();
}
//void foo() {} //uncomment this line for successful definition

Uważaj, aby zaimplementowana funkcja dokładnie odpowiadała tej, którą zadeklarowałeś. Na przykład możesz mieć niezgodne kwalifikatory 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)
                          

Inne przykłady niezgodności obejmują

  • Funkcja / zmienna zadeklarowana w jednej przestrzeni nazw, zdefiniowana w innej.
  • Funkcja / zmienna zadeklarowana jako element klasy, zdefiniowana jako globalna (lub odwrotnie).
  • Typ zwracanej funkcji, numer i typy parametrów oraz konwencja wywoływania nie są do końca zgodne.

Komunikat o błędzie z kompilatora często zawiera pełną deklarację zmiennej lub funkcji, która została zadeklarowana, ale nigdy nie została zdefiniowana. Porównaj ją ściśle z podaną definicją. Upewnij się, że każdy szczegół pasuje.

89 Svalorzen Jul 10 2014 at 18:46

Kolejność określania współzależnych bibliotek połączonych jest nieprawidłowa.

Kolejność, w jakiej biblioteki są połączone, MA znaczenie, jeśli biblioteki są od siebie zależne. Ogólnie rzecz biorąc, jeśli biblioteka Azależy od biblioteki B, to libA MUSI pojawić się wcześniej libBwe flagach konsolidatora.

Na przykład:

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

Utwórz biblioteki:

$ 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

Skompilować:

$ 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

Więc powtórzyć jeszcze raz, kolejność MA znaczenie!

76 Kastaneda Oct 07 2014 at 17:09

co to jest „niezdefiniowane odniesienie / nierozwiązany symbol zewnętrzny”

Spróbuję wyjaśnić, co to jest „niezdefiniowane odniesienie / nierozwiązany symbol zewnętrzny”.

uwaga: używam g ++ i Linuksa i wszystkie przykłady są do tego celu

Na przykład mamy kod

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

i

// 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);
}

Twórz pliki obiektowe

$ g++ -c src1.cpp -o src1.o $ g++ -c src2.cpp -o src2.o

Po fazie asemblera mamy plik obiektowy, który zawiera symbole do wyeksportowania. Spójrz na symbole

$ 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]

Odrzuciłem niektóre wiersze z wyjścia, ponieważ nie mają one znaczenia

Widzimy więc następujące symbole do wyeksportowania.

[1] - this is our static (local) variable (important - Bind has a type "LOCAL")
[2] - this is our global variable

src2.cpp nic nie eksportuje i nie widzieliśmy żadnych jego symboli

Połącz nasze pliki obiektowe

$ g++ src1.o src2.o -o prog

i uruchom go

$ ./prog
123

Linker widzi wyeksportowane symbole i łączy je. Teraz spróbujemy odkomentować wiersze w src2.cpp, tak jak tutaj

// 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);
}

i odbuduj plik obiektowy

$ g++ -c src2.cpp -o src2.o

OK (bez błędów), ponieważ budujemy tylko plik obiektowy, linkowanie nie zostało jeszcze zakończone. Spróbuj połączyć

$ 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

Stało się tak, ponieważ nasza nazwa_lokalnej_zmiennej jest statyczna, tj. Nie jest widoczna dla innych modułów. Teraz głębiej. Uzyskaj wynik fazy translacji

$ 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

Widzieliśmy więc, że nie ma etykiety dla local_var_name, dlatego konsolidator jej nie znalazł. Ale my jesteśmy hakerami :) i możemy to naprawić. Otwórz src1.s w edytorze tekstu i zmień

.local  _ZL14local_var_name
.comm   _ZL14local_var_name,4,4

do

    .globl  local_var_name
    .data
    .align 4
    .type   local_var_name, @object
    .size   local_var_name, 4
local_var_name:
    .long   456789

czyli powinieneś mieć jak poniżej

    .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:
; ...

zmieniliśmy widoczność local_var_name i ustawiliśmy jej wartość na 456789. Spróbuj zbudować z niego plik obiektowy

$ g++ -c src1.s -o src2.o

ok, zobacz wyjście readelf (symbole)

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

teraz local_var_name ma Bind GLOBAL (wcześniej LOCAL)

połączyć

$ g++ src1.o src2.o -o prog

i uruchom go

$ ./prog 
123456789

ok, zhakujemy to :)

W rezultacie - „niezdefiniowane odwołanie / nierozwiązany błąd symbolu zewnętrznego” ma miejsce, gdy konsolidator nie może znaleźć symboli globalnych w plikach obiektowych.

73 LuchianGrigore Sep 25 2012 at 06:39

Symbole zostały zdefiniowane w programie C i użyte w kodzie C ++.

Funkcja (lub zmienna) void foo()została zdefiniowana w programie C i próbujesz użyć jej w programie C ++:

void foo();
int main()
{
    foo();
}

Konsolidator C ++ oczekuje zniekształcenia nazw, więc musisz zadeklarować funkcję jako:

extern "C" void foo();
int main()
{
    foo();
}

Równoważnie, zamiast być zdefiniowanym w programie C, funkcja (lub zmienna) void foo()została zdefiniowana w C ++, ale z powiązaniem C:

extern "C" void foo();

i próbujesz użyć go w programie C ++ z powiązaniem z C ++.

Jeśli cała biblioteka jest zawarta w pliku nagłówkowym (i została skompilowana jako kod C); dołączenie będzie musiało wyglądać następująco;

extern "C" {
    #include "cheader.h"
}
70 sgryzko Dec 04 2013 at 01:11

Jeśli wszystko inne zawiedzie, dokonaj ponownej kompilacji.

Niedawno udało mi się pozbyć nierozwiązanego zewnętrznego błędu w programie Visual Studio 2012, po prostu rekompilując problematyczny plik. Po odtworzeniu błąd zniknął.

Dzieje się tak zwykle, gdy dwie (lub więcej) biblioteki mają cykliczną zależność. Biblioteka A próbuje użyć symboli z B.lib, a biblioteka B próbuje użyć symboli z A.lib. Żaden nie istnieje, od którego można by zacząć. Podczas próby skompilowania A, krok łącza nie powiedzie się, ponieważ nie może znaleźć biblioteki B.lib. Zostanie wygenerowana biblioteka A.lib, ale nie będzie biblioteki dll. Następnie kompilujesz B, co zakończy się sukcesem i wygeneruje B.lib. Ponowna kompilacja A będzie teraz działać, ponieważ znaleziono bibliotekę B.lib.

60 LuchianGrigore Sep 25 2012 at 06:39

Nieprawidłowe importowanie / eksportowanie metod / klas między modułami / biblioteką dll (specyficzne dla kompilatora).

MSVS wymaga określenia, które symbole mają być eksportowane i importowane przy użyciu __declspec(dllexport)i __declspec(dllimport).

Ta podwójna funkcjonalność jest zwykle uzyskiwana za pomocą makra:

#ifdef THIS_MODULE
#define DLLIMPEXP __declspec(dllexport)
#else
#define DLLIMPEXP __declspec(dllimport)
#endif

Makro THIS_MODULEbyłoby zdefiniowane tylko w module eksportującym funkcję. W ten sposób deklaracja:

DLLIMPEXP void foo();

rozszerza się do

__declspec(dllexport) void foo();

i mówi kompilatorowi, aby wyeksportował funkcję, ponieważ bieżący moduł zawiera jej definicję. W przypadku dołączenia deklaracji do innego modułu, rozszerzy się do

__declspec(dllimport) void foo();

i informuje kompilator, że definicja znajduje się w jednej z bibliotek, z którymi się łączysz (zobacz także 1) ).

Możesz podobne klasy importu / eksportu:

class DLLIMPEXP X
{
};
59 LuchianGrigore Sep 25 2012 at 06:38

Implementacje szablonów nie są widoczne.

Niespecjalizowane szablony muszą mieć swoje definicje widoczne dla wszystkich jednostek tłumaczeniowych, które ich używają. Oznacza to, że nie możesz oddzielić definicji szablonu od pliku implementacji. Jeśli musisz oddzielić implementację, typowym obejściem jest posiadanie implpliku, który umieścisz na końcu nagłówka, który deklaruje szablon. Typowa sytuacja to:

template<class T>
struct X
{
    void foo();
};

int main()
{
    X<int> x;
    x.foo();
}

//differentImplementationFile.cpp
template<class T>
void X<T>::foo()
{
}

Aby to naprawić, musisz przenieść definicję X::foodo pliku nagłówkowego lub w inne miejsce widoczne dla jednostki tłumaczeniowej, która z niej korzysta.

Specjalistyczne szablony można zaimplementować w pliku implementacji, a implementacja nie musi być widoczna, ale specjalizacja musi być wcześniej zadeklarowana.

Aby uzyskać dalsze wyjaśnienia i inne możliwe rozwiązanie (jawna instancja), zobacz to pytanie i odpowiedź .

58 NimaSoroush Apr 13 2015 at 23:42

Jest to jeden z najbardziej zagmatwanych komunikatów o błędach, które wszyscy programiści VC ++ widzieli raz po raz. Najpierw wyjaśnijmy, co się dzieje.

A. Co to jest symbol? Krótko mówiąc, symbol to nazwa. Może to być nazwa zmiennej, nazwa funkcji, nazwa klasy, nazwa typu typedef lub cokolwiek z wyjątkiem tych nazw i znaków, które należą do języka C ++. Jest definiowany przez użytkownika lub wprowadzany przez bibliotekę zależności (inną zdefiniowaną przez użytkownika).

B. Co jest zewnętrzne? W VC ++ każdy plik źródłowy (.cpp, .c, itd.) Jest traktowany jako jednostka tłumacząca, kompilator kompiluje jedną jednostkę na raz i generuje jeden plik obiektowy (.obj) dla bieżącej jednostki tłumaczeniowej. (Należy zauważyć, że każdy plik nagłówkowy, który zawiera ten plik źródłowy, zostanie wstępnie przetworzony i będzie traktowany jako część tej jednostki tłumaczeniowej). Wszystko w jednostce tłumaczeniowej jest uważane za wewnętrzne, wszystko inne jest uważane za zewnętrzne. W C ++, można odwoływać się do symbolu zewnętrznym za pomocą słów kluczowych, takich jak extern, __declspec (dllimport)i tak dalej.

C. Co to jest „rozwiązanie”? Rozwiązanie to termin, w którym znajdują się łącza. W czasie łączenia konsolidator próbuje znaleźć zewnętrzną definicję dla każdego symbolu w plikach obiektowych, który nie może wewnętrznie znaleźć swojej definicji. Zakres tego wyszukiwania obejmuje:

  • Wszystkie pliki obiektowe, które zostały wygenerowane w czasie kompilacji
  • Wszystkie biblioteki (.lib), które są jawnie lub niejawnie określone jako dodatkowe zależności tej aplikacji do budowania.

Ten proces wyszukiwania nazywa się rozwiązywać.

D. Wreszcie, dlaczego nierozwiązany symbol zewnętrzny? Jeśli konsolidator nie może znaleźć definicji zewnętrznej symbolu, który nie ma wewnętrznej definicji, zgłasza nierozwiązany błąd symbolu zewnętrznego.

E. Możliwe przyczyny LNK2019 : nierozwiązany błąd symbolu zewnętrznego. Wiemy już, że ten błąd jest spowodowany tym, że linker nie znalazł definicji symboli zewnętrznych, możliwe przyczyny można posortować jako:

  1. Definicja istnieje

Na przykład, jeśli mamy funkcję o nazwie foo zdefiniowaną w a.cpp:

int foo()
{
    return 0;
}

W b.cpp chcemy wywołać funkcję foo, więc dodajemy

void foo();

aby zadeklarować funkcję foo () i wywołać ją w innej treści funkcji, powiedzmy bar():

void bar()
{
    foo();
}

Teraz, kiedy zbudujesz ten kod, otrzymasz błąd LNK2019, narzekający, że foo jest nierozwiązanym symbolem. W tym przypadku wiemy, że foo () ma swoją definicję w a.cpp, ale różni się od tej, którą wywołujemy (inna wartość zwracana). Tak jest w przypadku, gdy istnieje definicja.

  1. Definicja nie istnieje

Jeśli chcemy wywołać niektóre funkcje w bibliotece, ale biblioteka importu nie jest dodawana do dodatkowej listy zależności (ustawionej z Project | Properties | Configuration Properties | Linker | Input | Additional Dependency:) ustawienia projektu. Teraz konsolidator zgłosi LNK2019, ponieważ definicja nie istnieje w obecnym zakresie wyszukiwania.

43 πάνταῥεῖ Mar 04 2014 at 06:52

niezdefiniowane odniesienie do WinMain@16lub podobne „nietypowe” main() odniesienie do punktu wejścia (szczególnie w przypadku Visual Studio ).

Być może przegapiłeś wybór odpowiedniego typu projektu w swoim rzeczywistym IDE. IDE może chcieć powiązać np. Projekty aplikacji Windows z taką funkcją punktu wejścia (jak określono w brakującym odnośniku powyżej) zamiast powszechnie używanego int main(int argc, char** argv);podpisu.

Jeśli Twoje IDE obsługuje projekty Plain Console, możesz wybrać ten typ projektu zamiast projektu aplikacji systemu Windows.


Oto Przypadek 1 i Przypadek 2 obsługiwane bardziej szczegółowo od świata rzeczywistego problemu.

37 Dula Jun 19 2014 at 00:06

Również jeśli używasz bibliotek innych firm, upewnij się, że masz poprawne pliki binarne 32/64 bitowe

35 Niall Sep 09 2014 at 19:09

Firma Microsoft oferuje #pragmamożliwość odniesienia się do właściwej biblioteki w czasie łączenia;

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

Oprócz ścieżki do biblioteki zawierającej katalog biblioteki, powinna to być pełna nazwa biblioteki.

35 Malvineous Jan 17 2015 at 09:24

Pakiet Visual Studio NuGet musi zostać zaktualizowany pod kątem nowej wersji zestawu narzędzi

Właśnie miałem ten problem podczas próby połączenia libpng z Visual Studio 2013. Problem polega na tym, że plik pakietu zawierał tylko biblioteki dla Visual Studio 2010 i 2012.

Prawidłowym rozwiązaniem jest nadzieja, że ​​programista wyda zaktualizowany pakiet, a następnie zaktualizuje, ale zadziałało, włamując się do dodatkowego ustawienia dla VS2013, wskazując na pliki biblioteki VS2012.

Edytowałem pakiet (w packagesfolderze wewnątrz katalogu rozwiązania), znajdując packagename\build\native\packagename.targetsi wewnątrz tego pliku, kopiując wszystkie v110sekcje. Zmieniłem v110się v120w polach stan tylko będąc bardzo ostrożny, aby zostawić wszystko jak ścieżki nazw plików v110. To po prostu pozwoliło programowi Visual Studio 2013 połączyć się z bibliotekami na rok 2012 iw tym przypadku zadziałało.

35 Noname Aug 11 2015 at 22:33

Załóżmy, że masz duży projekt napisany w języku c ++, który ma tysiąc plików .cpp i tysiąc plików .h. Powiedzmy, że projekt również zależy od dziesięciu bibliotek statycznych. Powiedzmy, że pracujemy w systemie Windows i budujemy nasz projekt w programie Visual Studio 20xx. Gdy naciśniesz Ctrl + F7 Visual Studio, aby rozpocząć kompilację całego rozwiązania (załóżmy, że mamy tylko jeden projekt w rozwiązaniu)

Jakie jest znaczenie kompilacji?

  • Visual Studio przeszukaj plik .vcxproj i zacznij kompilować każdy plik, który ma rozszerzenie .cpp. Kolejność kompilacji jest niezdefiniowana, więc nie możesz zakładać, że plik main.cpp jest kompilowany jako pierwszy
  • Jeśli pliki .cpp zależą od dodatkowych plików .h w celu znalezienia symboli, które mogą, ale nie muszą być zdefiniowane w pliku .cpp
  • Jeśli istnieje jeden plik .cpp, w którym kompilator nie mógł znaleźć jednego symbolu, błąd czasu kompilatora powoduje wyświetlenie komunikatu Nie można znaleźć symbolu x
  • Dla każdego pliku z rozszerzeniem .cpp jest generowany plik obiektu .o, a także program Visual Studio zapisuje dane wyjściowe w pliku o nazwie ProjectName.Cpp.Clean.txt, który zawiera wszystkie pliki obiektów, które muszą zostać przetworzone przez konsolidator.

Drugi krok kompilacji jest wykonywany przez Linkera. Linker powinien scalić cały plik obiektowy i ostatecznie zbudować wynik (który może być plikiem wykonywalnym lub biblioteką)

Kroki w łączeniu projektu

  • Przeanalizuj wszystkie pliki obiektowe i znajdź definicję, która została zadeklarowana tylko w nagłówkach (np .: kod jednej metody klasy, jak wspomniano w poprzednich odpowiedziach, lub zdarzenie inicjalizacji zmiennej statycznej, która jest składnikiem klasy)
  • Jeśli nie można znaleźć jednego symbolu w plikach obiektowych, jest on również przeszukiwany w Dodatkowych bibliotekach. Aby dodać nową bibliotekę do projektu Właściwości konfiguracji -> Katalogi VC ++ -> Katalogi bibliotek i tutaj określono dodatkowy folder do wyszukiwania bibliotek i Właściwości konfiguracyjne -> Linker -> Wejście do określenia nazwy biblioteki. -Jeśli konsolidator nie może znaleźć symbolu, który piszesz w jednym pliku .cpp, generuje błąd czasu konsolidatora, który może brzmieć jakerror LNK2001: unresolved external symbol "void __cdecl foo(void)" (?foo@@YAXXZ)

Obserwacja

  1. Kiedy linker znajdzie jeden symbol, nie będzie szukał go w innych bibliotekach
  2. Kolejność łączenia bibliotek ma znaczenie .
  3. Jeśli Linker znajdzie zewnętrzny symbol w jednej statycznej bibliotece, umieści ten symbol w danych wyjściowych projektu. Jeśli jednak biblioteka jest współdzielona (dynamiczna), nie dołącza kodu (symboli) do wyjścia, ale awarie Run-Time mogą pojawić się

Jak rozwiązać tego rodzaju błąd

Błąd czasu kompilatora:

  • Upewnij się, że składnia projektu w języku C ++ jest poprawna.

Błąd czasu konsolidatora

  • Zdefiniuj wszystkie symbole, które deklarujesz w swoich plikach nagłówkowych
  • Służy #pragma oncedo zezwalania kompilatorowi na nie dołączanie jednego nagłówka, jeśli był już zawarty w bieżącym pliku .cpp, które są kompilowane
  • Upewnij się, że Twoja zewnętrzna biblioteka nie zawiera symboli, które mogą wchodzić w konflikt z innymi symbolami zdefiniowanymi w plikach nagłówkowych
  • Kiedy używasz szablonu, aby upewnić się, że w pliku nagłówkowym umieścisz definicję każdej funkcji szablonu, aby umożliwić kompilatorowi wygenerowanie odpowiedniego kodu dla dowolnych wystąpień.
29 developerbmw Jan 03 2015 at 05:06

Błąd w kompilatorze / IDE

Niedawno miałem ten problem i okazało się, że to błąd w Visual Studio Express 2013 . Musiałem usunąć plik źródłowy z projektu i dodać go ponownie, aby naprawić błąd.

Kroki do wypróbowania, jeśli uważasz, że może to być błąd w kompilatorze / IDE:

  • Wyczyść projekt (niektóre IDE mają opcję, aby to zrobić, możesz również zrobić to ręcznie, usuwając pliki obiektów)
  • Spróbuj rozpocząć nowy projekt, kopiując cały kod źródłowy z oryginalnego.
27 Niall Jul 27 2015 at 17:20

Użyj konsolidatora, aby pomóc zdiagnozować błąd

Większość nowoczesnych konsolidatorów zawiera opcję gadatliwą, która drukuje w różnym stopniu;

  • Wywołanie linku (linia poleceń),
  • Dane o tym, jakie biblioteki znajdują się na etapie linków,
  • Lokalizacja bibliotek,
  • Używane ścieżki wyszukiwania.

Dla gcc i clang; zazwyczaj dodajesz -v -Wl,--verboselub -v -Wl,-vdo wiersza poleceń. Więcej szczegółów można znaleźć tutaj;

  • Strona podręcznika ld systemu Linux .
  • LLVM strona łącznikiem .
  • „Wprowadzenie do GCC” rozdział 9 .

W przypadku MSVC /VERBOSE(w szczególności /VERBOSE:LIB) jest dodawane do wiersza poleceń łącza.

  • Strona MSDN w /VERBOSEopcji konsolidatora .
26 kiriloff Apr 04 2014 at 22:02

Połączony plik .lib jest powiązany z plikiem .dll

Miałem ten sam problem. Powiedzmy, że mam projekty MyProject i TestProject. Skutecznie połączyłem plik lib dla MyProject z TestProject. Jednak ten plik lib został utworzony podczas tworzenia biblioteki DLL dla MyProject. Ponadto nie zawierałem kodu źródłowego dla wszystkich metod w MyProject, a jedynie dostęp do punktów wejścia DLL.

Aby rozwiązać ten problem, zbudowałem MyProject jako LIB i połączyłem TestProject z tym plikiem .lib (kopiuję, wklejam wygenerowany plik .lib do folderu TestProject). Następnie mogę ponownie zbudować MyProject jako bibliotekę DLL. Jest kompilowany, ponieważ biblioteka, do której jest połączony TestProject, zawiera kod dla wszystkich metod w klasach w MyProject.

25 Plankalkül Sep 10 2015 at 18:03

Ponieważ wydaje się, że ludzie są kierowani do tego pytania, jeśli chodzi o błędy linkera, dodam to tutaj.

Jedną z możliwych przyczyn błędów konsolidatora w GCC 5.2.0 jest to, że nowa biblioteka ABI libstdc ++ jest teraz wybrana domyślnie.

Jeśli otrzymujesz błędy konsolidatora dotyczące niezdefiniowanych odwołań do symboli, które obejmują typy w przestrzeni nazw std :: __ cxx11 lub tagu [abi: cxx11], oznacza to prawdopodobnie, że próbujesz połączyć pliki obiektów, które zostały skompilowane z różnymi wartościami dla _GLIBCXX_USE_CXX11_ABI makro. Dzieje się tak często podczas łączenia z biblioteką innej firmy, która została skompilowana ze starszą wersją GCC. Jeśli biblioteki innej firmy nie można odbudować za pomocą nowego ABI, będziesz musiał ponownie skompilować swój kod ze starym ABI.

Więc jeśli nagle pojawią się błędy konsolidatora podczas przełączania na GCC po 5.1.0, warto to sprawdzić.

21 MikeKinghan Apr 09 2017 at 17:34

Twoje powiązanie zużywa biblioteki przed plikami obiektowymi, które się do nich odwołują

  • Próbujesz skompilować i połączyć swój program z łańcuchem narzędzi GCC.
  • Twoje powiązanie określa wszystkie niezbędne biblioteki i ścieżki wyszukiwania bibliotek
  • Jeśli libfoozależy od libbar, to twoje połączenie jest poprawnie wstawiane libfooprzed libbar.
  • Twoje połączenie kończy się niepowodzeniem z undefined reference to powodu błędów.
  • Ale wszystkie niezdefiniowane coś s są zadeklarowane w plikach nagłówkowych, które masz #included i są w rzeczywistości zdefiniowane w bibliotekach, które łączysz.

Przykłady są w C. Równie dobrze mógłby to być C ++

Minimalny przykład dotyczący utworzonej samodzielnie biblioteki statycznej

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

np1.c

#include <my_lib.h>

int main()
{
    hw();
    return 0;
}

Budujesz swoją bibliotekę statyczną:

$ gcc -c -o my_lib.o my_lib.c $ ar rcs libmy_lib.a my_lib.o

Kompilujesz swój program:

$ gcc -I. -c -o eg1.o eg1.c

Próbujesz to połączyć z 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

Ten sam wynik, jeśli kompilujesz i łączysz w jednym kroku, na przykład:

$ 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

Minimalny przykład dotyczący współużytkowanej biblioteki systemowej, biblioteki kompresji libz

eg2.c

#include <zlib.h>
#include <stdio.h>

int main()
{
    printf("%s\n",zlibVersion());
    return 0;
}

Skompiluj swój program:

$ gcc -c -o eg2.o eg2.c

Spróbuj połączyć swój program z 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

To samo, jeśli kompilujesz i łączysz za jednym razem:

$ 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

I odmiana przykładu 2 obejmująca pkg-config:

$ gcc -o eg2 $(pkg-config --libs zlib) eg2.o 
eg2.o: In function `main':
eg2.c:(.text+0x5): undefined reference to `zlibVersion'

Co robisz źle?

W sekwencji plików obiektowych i bibliotek, które chcesz połączyć, aby utworzyć program, umieszczasz biblioteki przed plikami obiektowymi, które się do nich odwołują. Biblioteki należy umieścić po plikach obiektów, które się do nich odwołują.

Przykład linku 1 poprawnie:

$ gcc -o eg1 eg1.o -L. -lmy_lib

Sukces:

$ ./eg1 
Hello World

Połącz poprawnie przykład 2:

$ gcc -o eg2 eg2.o -lz

Sukces:

$ ./eg2 
1.2.8

Połącz pkg-configpoprawnie odmianę przykładu 2 :

$ gcc -o eg2 eg2.o $(pkg-config --libs zlib) 
$ ./eg2
1.2.8

Wyjaśnienie

Odtąd czytanie jest opcjonalne .

Domyślnie polecenie łączenia wygenerowane przez GCC w twojej dystrybucji zużywa pliki w powiązaniu od lewej do prawej w sekwencji wiersza poleceń. Kiedy stwierdzi, że plik odnosi się do czegoś i nie zawiera definicji tego, to wyszuka definicję w plikach znajdujących się dalej po prawej stronie. Jeśli w końcu znajdzie definicję, odniesienie zostanie rozwiązane. Jeśli jakiekolwiek odniesienia pozostaną nierozwiązane na końcu, powiązanie nie powiedzie się: konsolidator nie przeszukuje wstecz.

Najpierw przykład 1 z biblioteką statycznąmy_lib.a

Biblioteka statyczna to zindeksowane archiwum plików obiektowych. Kiedy konsolidator znajdzie -lmy_libw sekwencji łączenia i odkryje , że odnosi się to do biblioteki statycznej ./libmy_lib.a, chce wiedzieć, czy twój program potrzebuje któregokolwiek z plików obiektowych w libmy_lib.a.

W programie znajduje się tylko plik obiektowy libmy_lib.a, a my_lib.ojest tylko jedna rzecz zdefiniowana w programie my_lib.o, a mianowicie funkcja hw.

Konsolidator zdecyduje, że twój program potrzebuje my_lib.owtedy i tylko wtedy, gdy już wie, że program się odwołuje hw, w jednym lub kilku plikach obiektowych, które już dodał do programu, i że żaden z plików obiektowych, które już dodał, nie zawiera definicja dla hw.

Jeśli to prawda, linker wyodrębni kopię my_lib.oz biblioteki i doda ją do twojego programu. Następnie twój program zawiera definicję for hw, więc jego odwołania do hwzostaną rozwiązane .

Kiedy próbujesz połączyć program, na przykład:

$ gcc -o eg1 -L. -lmy_lib eg1.o

konsolidator nie dodał eg1.o do programu, gdy widzi -lmy_lib. Ponieważ w tym momencie nie widział eg1.o. Twój program nie ma jeszcze dokonywać żadnych odniesień do hw: to jeszcze nie dokonywać żadnych odniesień w ogóle , ponieważ wszystkie odniesienia to sprawia, że są eg1.o.

Więc konsolidator nie dodaje my_lib.odo programu i nie ma już zastosowania libmy_lib.a.

Następnie znajduje eg1.oi dodaje go jako program. Plik obiektowy w sekwencji łączenia jest zawsze dodawany do programu. Teraz program odwołuje się do hwi nie zawiera definicji hw; ale w sekwencji powiązań nie ma nic, co mogłoby dostarczyć brakującej definicji. Odniesienie do hwpozostaje nierozwiązane , a powiązanie zawodzi.

Po drugie, przykład 2 , ze współdzieloną bibliotekąlibz

Biblioteka współdzielona nie jest archiwum plików obiektowych ani niczego podobnego. Bardziej przypomina program , który nie ma mainfunkcji, a zamiast tego udostępnia wiele innych symboli, które definiuje, aby inne programy mogły ich używać w czasie wykonywania.

Dzisiaj wielu dystrybucjach Linuksa skonfigurować swój GCC toolchain tak, że jego sterowniki językowe ( gcc, g++, gfortranetc) poinstruować łącznikiem systemowym ( ld), aby połączyć współdzielonych bibliotek w miarę potrzeb podstawie. Masz jedną z tych dystrybucji.

Oznacza to, że kiedy linker znajdzie -lzw sekwencji łączenia i /usr/lib/x86_64-linux-gnu/libz.sozorientuje się, że odnosi się to do biblioteki współdzielonej (powiedzmy) , chce wiedzieć, czy jakiekolwiek odwołania, które dodał do twojego programu, a które nie zostały jeszcze zdefiniowane, mają definicje, które są eksportowane przezlibz

Jeśli to prawda, linker nie skopiuje żadnych fragmentów libzi nie doda ich do twojego programu; zamiast tego po prostu przetworzy kod twojego programu tak, że: -

  • W czasie wykonywania system ładujący program ładuje kopię libzdo tego samego procesu, co program, za każdym razem, gdy ładuje kopię programu, aby go uruchomić.

  • W czasie wykonywania, za każdym razem, gdy program odwołuje się do czegoś, co jest zdefiniowane w programie libz, odwołanie to wykorzystuje definicję wyeksportowaną przez kopię libzw tym samym procesie.

Twój program chce odnosić się tylko do jednej rzeczy, która ma definicję wyeksportowaną przez libz, a mianowicie do funkcji zlibVersion, do której odwołuje się tylko raz, w eg2.c. Jeśli konsolidator doda to odniesienie do twojego programu, a następnie znajdzie definicję wyeksportowaną przez libz, odniesienie zostanie rozwiązane

Ale kiedy próbujesz połączyć program, na przykład:

gcc -o eg2 -lz eg2.o

kolejność zdarzeń jest błędna w taki sam sposób, jak w przykładzie 1. W momencie, gdy linker znajdzie -lz, nie ma żadnych odniesień do niczego w programie: wszystkie są w eg2.o, czego jeszcze nie widziano. Tak więc konsolidator decyduje, że nie ma dla niego zastosowania libz. Kiedy osiągnie eg2.o, dodaje go do programu, a następnie ma niezdefiniowane odniesienie do zlibVersion, sekwencja łączenia jest zakończona; to odniesienie jest nierozwiązane, a powiązanie zawodzi.

Wreszcie pkg-configodmiana przykładu 2 ma teraz oczywiste wyjaśnienie. Po rozszerzeniu powłoki:

gcc -o eg2 $(pkg-config --libs zlib) eg2.o

staje się:

gcc -o eg2 -lz eg2.o

co jest tylko przykładem 2.

Mogę odtworzyć problem w przykładzie 1, ale nie w przykładzie 2

Połączenie:

gcc -o eg2 -lz eg2.o

działa dobrze dla Ciebie!

(Lub: to połączenie działało dobrze dla ciebie, powiedzmy, Fedora 23, ale nie działa w Ubuntu 16.04)

Dzieje się tak, ponieważ dystrybucja, w której działa powiązanie, jest jedną z tych, które nie konfigurują swojego łańcucha narzędzi GCC do łączenia bibliotek współdzielonych w razie potrzeby .

W tamtych czasach było to normalne, że systemy typu unix łączyły biblioteki statyczne i współdzielone według różnych reguł. Biblioteki statyczne w sekwencji łączenia były łączone w miarę potrzeb, jak wyjaśniono w przykładzie 1, ale biblioteki współdzielone były łączone bezwarunkowo.

Takie zachowanie jest ekonomiczne w czasie łączenia, ponieważ konsolidator nie musi zastanawiać się, czy program potrzebuje biblioteki współdzielonej: jeśli jest to biblioteka współdzielona, ​​połącz ją. Większość bibliotek w większości powiązań to biblioteki współdzielone. Ale są też wady: -

  • Jest to nieekonomiczne w czasie wykonywania , ponieważ może powodować ładowanie bibliotek współdzielonych wraz z programem, nawet jeśli ich nie potrzebują.

  • Różne reguły łączenia dla bibliotek statycznych i współdzielonych mogą być mylące dla niedoświadczonych programistów, którzy mogą nie wiedzieć, czy -lfoow ich połączeniu rozwiążą się z /some/where/libfoo.alub do /some/where/libfoo.so, i mogą i tak nie rozumieć różnicy między bibliotekami współdzielonymi i statycznymi.

Ten kompromis doprowadził do dzisiejszej schizmatyckiej sytuacji. Niektóre dystrybucje zmieniły swoje reguły łączenia GCC dla bibliotek współdzielonych, tak że zasada w razie potrzeby ma zastosowanie do wszystkich bibliotek. Niektóre dystrybucje utknęły przy starym sposobie.

Dlaczego nadal mam ten problem, nawet jeśli kompiluję i łączę w tym samym czasie?

Jeśli po prostu zrobię:

$ gcc -o eg1 -I. -L. -lmy_lib eg1.c

z pewnością gcc musi eg1.cnajpierw skompilować , a następnie dowiązać wynikowy plik obiektowy z libmy_lib.a. Skąd więc może nie wiedzieć, że plik obiektowy jest potrzebny, gdy wykonuje linkowanie?

Ponieważ kompilowanie i łączenie za pomocą jednego polecenia nie zmienia kolejności sekwencji powiązań.

Kiedy uruchomisz powyższe polecenie, gcczorientujesz się, że chcesz kompilację + powiązanie. Więc za kulisami generuje polecenie kompilacji i uruchamia je, a następnie generuje polecenie łączenia i uruchamia je, tak jakbyś uruchomił dwa polecenia:

$ gcc -I. -c -o eg1.o eg1.c $ gcc -o eg1 -L. -lmy_lib eg1.o

Tak więc połączenie nie tak jak to robi, jeśli nie uruchomić te dwie komendy. Jedyną różnicą, jaką zauważysz w niepowodzeniu, jest to, że gcc wygenerował tymczasowy plik obiektowy w przypadku kompilacji + link, ponieważ nie każesz mu używać eg1.o. Widzimy:

/tmp/ccQk1tvs.o: In function `main'

zamiast:

eg1.o: In function `main':

Zobacz też

Kolejność określania współzależnych bibliotek połączonych jest nieprawidłowa

Umieszczenie bibliotek współzależnych w niewłaściwej kolejności to tylko jeden ze sposobów, w jaki można uzyskać pliki, które wymagają definicji rzeczy, które pojawią się później w powiązaniu, niż pliki, które zawierają definicje. Umieszczenie bibliotek przed plikami obiektowymi, które się do nich odwołują, to kolejny sposób na popełnienie tego samego błędu.

20 JDiMatteo Mar 30 2015 at 23:03

Opakowanie wokół GNU ld, które nie obsługuje skryptów konsolidatora

Niektóre pliki .so to w rzeczywistości skrypty konsolidujące ld GNU , np. Plik libtbb.so jest plikiem tekstowym ASCII z następującą zawartością:

INPUT (libtbb.so.2)

Niektóre bardziej złożone kompilacje mogą tego nie obsługiwać. Na przykład, jeśli włączysz -v do opcji kompilatora, zobaczysz, że mainwin gcc wrapper mwdip odrzuca pliki poleceń skryptu konsolidatora na szczegółowej liście wyjściowej bibliotek do połączenia. Prostym obejściem jest zastąpienie polecenia wejścia skryptu konsolidatora plik z kopią pliku zamiast (lub dowiązaniem symbolicznym), np

cp libtbb.so.2 libtbb.so

Lub możesz zamienić argument -l na pełną ścieżkę do .so, np. Zamiast -ltbbdo/home/foo/tbb-4.3/linux/lib/intel64/gcc4.4/libtbb.so.2

18 Niall Mar 09 2016 at 19:08

Przyjazne szablony ...

Biorąc pod uwagę fragment kodu typu szablonu z operatorem znajomego (lub funkcją);

template <typename T>
class Foo {
    friend std::ostream& operator<< (std::ostream& os, const Foo<T>& a);
};

operator<<Jest zadeklarowana jako funkcja bez szablonu. Dla każdego typu Tużywanego w programie Foomusi istnieć plik bez szablonu operator<<. Na przykład, jeśli istnieje Foo<int>zadeklarowany typ , musi istnieć implementacja operatora w następujący sposób;

std::ostream& operator<< (std::ostream& os, const Foo<int>& a) {/*...*/}

Ponieważ nie jest zaimplementowany, konsolidator nie znajduje go i powoduje błąd.

Aby to naprawić, możesz zadeklarować operator szablonu przed Footypem, a następnie zadeklarować jako znajomego odpowiednią instancję. Składnia jest trochę niezręczna, ale wygląda następująco;

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

Powyższy kod ogranicza przyjaźń operatora do odpowiedniej instancji Foo, tj. operator<< <int>Instancja jest ograniczona do dostępu do prywatnych członków instancji Foo<int>.

Alternatywy obejmują;

  • Umożliwienie przyjaźni rozciągnięcia się na wszystkie instancje szablonów w następujący sposób;

    template <typename T>
    class Foo {
        template <typename T1>
        friend std::ostream& operator<<(std::ostream& os, const Foo<T1>& a);
        // ...
    };
    
  • Lub, implementacja dla the operator<<może być wykonana wewnątrz definicji klasy;

    template <typename T>
    class Foo {
        friend std::ostream& operator<<(std::ostream& os, const Foo& a)
        { /*...*/ }
        // ...
    };
    

Zauważ , że kiedy deklaracja operatora (lub funkcji) pojawia się tylko w klasie, nazwa nie jest dostępna dla "normalnego" wyszukiwania, tylko dla wyszukiwania zależnego od argumentów, z cppreference ;

Nazwa najpierw zadeklarowana w deklaracji zaprzyjaźnionej w ramach klasy lub szablonu klasy X staje się członkiem najbardziej wewnętrznej otaczającej przestrzeni nazw X, ale nie jest dostępna do wyszukiwania (z wyjątkiem wyszukiwania zależnego od argumentów, które uwzględnia X), chyba że zgodna deklaracja w zakresie przestrzeni nazw jest opatrzony...

Więcej informacji na temat znajomych szablonów można znaleźć na stronie cppreference i C ++ FAQ .

Lista kodu pokazująca powyższe techniki .


Na marginesie przykład błędnego kodu; g ++ ostrzega o tym w następujący sposób

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)

16 fafaro Mar 03 2017 at 13:57

Kiedy ścieżki dołączania są różne

Błędy konsolidatora mogą wystąpić, gdy plik nagłówkowy i skojarzona z nim biblioteka współdzielona (plik .lib) nie są zsynchronizowane. Pozwól mi wyjaśnić.

Jak działają konsolidatory? Konsolidator dopasowuje deklarację funkcji (zadeklarowaną w nagłówku) do jej definicji (w bibliotece współdzielonej), porównując ich sygnatury. Możesz otrzymać błąd konsolidatora, jeśli konsolidator nie znajdzie definicji funkcji, która idealnie pasuje.

Czy nadal można uzyskać błąd konsolidatora, mimo że deklaracja i definicja wydają się zgodne? Tak! Mogą wyglądać tak samo w kodzie źródłowym, ale tak naprawdę zależy to od tego, co widzi kompilator. Zasadniczo możesz skończyć z taką sytuacją:

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

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

Zwróć uwagę, że chociaż obie deklaracje funkcji wyglądają identycznie w kodzie źródłowym, ale tak naprawdę różnią się w zależności od kompilatora.

Możesz zapytać, jak można znaleźć się w takiej sytuacji? Oczywiście uwzględnij ścieżki ! Jeśli podczas kompilowania biblioteki współdzielonej ścieżka dołączania prowadzi do, header1.ha skończysz używać go header2.hwe własnym programie, będziesz się zastanawiać, co się stało (gra słów zamierzona).

Przykład tego, jak może się to zdarzyć w prawdziwym świecie, wyjaśniono poniżej.

Dalsze omówienie z przykładem

Mam dwa projekty: graphics.libi main.exe. Oba projekty zależą od common_math.h. Załóżmy, że biblioteka eksportuje następującą funkcję:

// graphics.lib    
#include "common_math.h" 
   
void draw(vec3 p) { ... } // vec3 comes from common_math.h

A potem włączasz bibliotekę do swojego własnego projektu.

// main.exe
#include "other/common_math.h"
#include "graphics.h"

int main() {
    draw(...);
}

Bum! Otrzymujesz błąd konsolidatora i nie masz pojęcia, dlaczego się nie udaje. Powodem jest to, że wspólna biblioteka używa różnych wersji tego samego include common_math.h(pokazałem to w tym przykładzie, dołączając inną ścieżkę, ale nie zawsze może to być takie oczywiste. Być może ścieżka dołączania jest inna w ustawieniach kompilatora) .

Zauważ, że w tym przykładzie linker powie ci, że nie mógł znaleźć draw(), podczas gdy w rzeczywistości wiesz, że jest on oczywiście eksportowany przez bibliotekę. Możesz spędzić godziny drapiąc się po głowie, zastanawiając się, co poszło nie tak. Rzecz w tym, że konsolidator widzi inną sygnaturę, ponieważ typy parametrów są nieco inne. W tym przykładzie vec3jest to inny typ w obu projektach, jeśli chodzi o kompilator. Może się tak zdarzyć, ponieważ pochodzą z dwóch nieco różnych plików dołączanych (być może pliki dołączane pochodzą z dwóch różnych wersji biblioteki).

Debugowanie konsolidatora

DUMPBIN jest Twoim przyjacielem, jeśli używasz programu Visual Studio. Jestem pewien, że inne kompilatory mają inne podobne narzędzia.

Proces przebiega następująco:

  1. Zwróć uwagę na dziwną zniekształconą nazwę podaną w błędzie konsolidatora. (np. rysuj @ grafika @ XYZ).
  2. Zrzuć wyeksportowane symbole z biblioteki do pliku tekstowego.
  3. Wyszukaj wyeksportowany symbol zainteresowania i zwróć uwagę, że zniekształcona nazwa jest inna.
  4. Zwróć uwagę, dlaczego zniekształcone nazwy okazały się inne. Możesz zobaczyć, że typy parametrów są różne, mimo że wyglądają tak samo w kodzie źródłowym.
  5. Powód, dla którego są różne. W powyższym przykładzie różnią się one z powodu różnych plików dołączanych.

[1] Przez projekt mam na myśli zestaw plików źródłowych, które są ze sobą połączone w celu utworzenia biblioteki lub pliku wykonywalnego.

EDYCJA 1: Przepisano pierwszą sekcję, aby była łatwiejsza do zrozumienia. Proszę o komentarz poniżej, aby dać mi znać, jeśli coś innego wymaga naprawy. Dzięki!

15 Niall Apr 07 2016 at 18:53

Niespójne UNICODEdefinicje

Kompilacja Windows UNICODE jest budowana z TCHARitp. Zdefiniowanymi jako wchar_titp. Gdy nie buduje się z UNICODEdefinicją jako kompilacja z TCHARdefinicją jako charitp. Te UNICODEi _UNICODEdefinicje wpływają na wszystkie typy łańcuchów " T" ; LPTSTR, LPCTSTRI ich Ełku.

Zbudowanie jednej biblioteki ze UNICODEzdefiniowaną i próba połączenia jej w projekcie, w którym UNICODEnie jest zdefiniowana, spowoduje błędy linkera, ponieważ wystąpi niezgodność w definicji TCHAR; charvs wchar_t.

Błąd zwykle obejmuje funkcję, wartość z typem pochodnym charlub wchar_t, mogą one std::basic_string<>również obejmować itp. Podczas przeglądania funkcji w kodzie, której dotyczy problem, często pojawia się odniesienie do TCHARlub std::basic_string<TCHAR>itp. Jest to znak ostrzegawczy, że kod był pierwotnie przeznaczony zarówno dla kompilacji UNICODE, jak i wielobajtowej (lub „wąskiej”) .

Aby to naprawić, utwórz wszystkie wymagane biblioteki i projekty ze spójną definicją UNICODE(i _UNICODE).

  1. Można to zrobić za pomocą jednego z nich;

    #define UNICODE
    #define _UNICODE
    
  2. Lub w ustawieniach projektu;

    Właściwości projektu> Ogólne> Wartości domyślne projektu> Zestaw znaków

  3. Lub w wierszu poleceń;

    /DUNICODE /D_UNICODE
    

Alternatywa ma również zastosowanie, jeśli UNICODE nie jest przeznaczony do użycia, upewnij się, że definicje nie są ustawione i / lub ustawienie wieloznakowe jest używane w projektach i konsekwentnie stosowane.

Nie zapomnij zachować spójności między wersjami „Release” i „Debug”.

14 Niall Feb 24 2016 at 17:40

Oczyść i odbuduj

„Wyczyszczenie” kompilacji może usunąć „martwe drewno”, które może leżeć po poprzednich kompilacjach, nieudanych kompilacjach, niekompletnych kompilacjach i innych problemach z kompilacją związanych z systemem kompilacji.

Generalnie IDE lub kompilacja będzie zawierała jakąś formę funkcji „czystych”, ale może ona nie być poprawnie skonfigurowana (np. W ręcznym pliku makefile) lub może zawieść (np. Pośrednie lub wynikowe pliki binarne są tylko do odczytu).

Po zakończeniu „czyszczenia” sprawdź, czy „czyszczenie” powiodło się i czy cały wygenerowany plik pośredni (np. Zautomatyzowany plik makefile) został pomyślnie usunięty.

Ten proces może być postrzegany jako ostateczność, ale często jest dobrym pierwszym krokiem ; zwłaszcza jeśli kod związany z błędem został niedawno dodany (lokalnie lub z repozytorium źródłowego).

10 AndreasH. Aug 03 2017 at 15:01

Brak „extern” w constdeklaracjach / definicjach zmiennych (tylko C ++)

Dla osób wywodzących się z C może być zaskoczeniem, że w C ++ constzmienne globalne mają wewnętrzne (lub statyczne) powiązanie. W C tak się nie stało, ponieważ wszystkie zmienne globalne są niejawnie extern(tj. Gdy staticbrakuje słowa kluczowego).

Przykład:

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

poprawne byłoby użycie pliku nagłówkowego i umieszczenie go w plikach file2.cpp i file1.cpp

extern const int test;
extern int test2;

Alternatywnie można zadeklarować constzmienną w pliku1.cpp z jawnym użyciemextern

8 Stypox Aug 27 2018 at 23:43

Mimo że są to dość stare pytania z wieloma zaakceptowanymi odpowiedziami, chciałbym podzielić się, jak rozwiązać niejasny błąd „niezdefiniowanego odniesienia do”.

Różne wersje bibliotek

Używałem aliasu do odniesienia się do std::filesystem::path: system plików znajduje się w standardowej bibliotece od C ++ 17, ale mój program musiał również skompilować się w C ++ 14, więc zdecydowałem się użyć aliasu zmiennej:

#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

Powiedzmy, że mam trzy pliki: main.cpp, file.h, file.cpp:

  • file.h # include's < experimental :: filesystem > i zawiera powyższy kod
  • file.cpp , implementacja file.h, # include's „ file.h
  • main.cpp # include's < system plików > i „ plik.h

Zwróć uwagę na różne biblioteki używane w main.cpp i file.h. Ponieważ main.cpp # zawierał „ file.h ” po < filesystem >, używaną wersją systemu plików była wersja C ++ 17 . Kiedyś kompilowałem program za pomocą następujących poleceń:

$ g++ -g -std=c++17 -c main.cpp-> kompiluje main.cpp do main.o
$ g++ -g -std=c++17 -c file.cpp-> kompiluje file.cpp i file.h do file.o
$ g++ -g -std=c++17 -o executable main.o file.o -lstdc++fs-> łączy main.o i file.o

W ten sposób każdy funkcja zawarta w file.o i wykorzystywane w main.o że wymaganepath_t dał „niezdefiniowana odniesienia” błędy ponieważ main.o mowa std::filesystem::pathale file.o do std::experimental::filesystem::path.

Rozkład

Aby to naprawić, wystarczyło zmienić <eksperymentalny :: system plików> w plik.h na <system plików> .

5 ead Sep 07 2018 at 13:22

Podczas łączenia z bibliotekami współdzielonymi upewnij się, że użyte symbole nie są ukryte.

Domyślnym zachowaniem gcc jest to, że wszystkie symbole są widoczne. Jednak gdy jednostki tłumaczeniowe są zbudowane z opcją -fvisibility=hidden, tylko funkcje / symbole oznaczone __attribute__ ((visibility ("default")))są zewnętrznymi w wynikowym obiekcie współdzielonym.

Możesz sprawdzić, czy symbole, których szukasz, są zewnętrzne, wywołując:

# -D shows (global) dynamic symbols that can be used from the outside of XXX.so
nm -D XXX.so | grep MY_SYMBOL 

symbole ukryte / lokalne są pokazane nmz małymi literami, na przykład tzamiast `T dla sekcji kodu:

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

Możesz także użyć nmz opcją -Cdo rozszyfrowania nazw (jeśli użyto C ++).

Podobnie jak w Windows-dll, można by oznaczyć funkcje publiczne definicją, na przykład DLL_PUBLICzdefiniowaną jako:

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

DLL_PUBLIC int my_public_function(){
  ...
}

Co z grubsza odpowiada wersji Windows / MSVC:

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

Więcej informacji o widoczności można znaleźć na wiki gcc.


Gdy jednostka translacji jest kompilowana z -fvisibility=hiddenwynikowymi symbolami, nadal mają one zewnętrzne powiązanie (pokazane z typem symbolu wielkimi literami przez nm) i mogą być bez problemu używane do łączenia zewnętrznego, jeśli pliki obiektowe staną się częścią bibliotek statycznych. Powiązanie staje się lokalne tylko wtedy, gdy pliki obiektów są połączone z biblioteką współdzieloną.

Aby dowiedzieć się, które symbole w pliku obiektowym są ukryte, uruchom:

>>> objdump -t XXXX.o | grep hidden
0000000000000000 g     F .text  000000000000000b .hidden HIDDEN_SYMBOL1
000000000000000b g     F .text  000000000000000b .hidden HIDDEN_SYMBOL2
3 MikeKinghan Jan 22 2019 at 02:44

Funkcje lub metody klas są definiowane w plikach źródłowych ze inlinespecyfikatorem.

Przykład:-

main.cpp

#include "gum.h"
#include "foo.h"

int main()
{
    gum();
    foo f;
    f.bar();
    return 0;
}

foo.h (1)

#pragma once

struct foo {
    void bar() const;
};

gum.h (1)

#pragma once

extern void gum();

foo.cpp (1)

#include "foo.h"
#include <iostream>

inline /* <- wrong! */ void foo::bar() const {
    std::cout << __PRETTY_FUNCTION__ << std::endl;
}

gum.cpp (1)

#include "gum.h"
#include <iostream>

inline /* <- wrong! */ void gum()
{
    std::cout << __PRETTY_FUNCTION__ << std::endl;
}

Jeśli określisz, że gum(podobnie foo::bar) jest inlinew swojej definicji, kompilator będzie wbudowany gum(jeśli zdecyduje), przez: -

  • nie emitując żadnej unikalnej definicji gum, a zatem
  • nie emituje żadnego symbolu, za pomocą którego konsolidator może odwoływać się do definicji gum, i zamiast tego
  • zastąpienie wszystkich wywołań funkcji gumwbudowanymi kopiami skompilowanej treści gum.

W rezultacie, jeśli zdefiniujesz guminline w pliku źródłowym gum.cpp, jest on kompilowany do pliku obiektowego, gum.ow którym wszystkie wywołania gumsą wbudowane i nie jest zdefiniowany żaden symbol, do którego może się odwoływać konsolidator gum. Kiedy łączysz gum.osię z programem razem z innym plikiem obiektowym, np. main.oKtóre odwołują się do zewnętrznego symbolu gum, konsolidator nie może rozwiązać tych odniesień. Więc połączenie zawodzi:

Skompilować:

g++ -c  main.cpp foo.cpp gum.cpp

Połączyć:

$ g++ -o prog main.o foo.o gum.o
main.o: In function `main':
main.cpp:(.text+0x18): undefined reference to `gum()'
main.cpp:(.text+0x24): undefined reference to `foo::bar() const'
collect2: error: ld returned 1 exit status

Możesz zdefiniować tylko gumtak, inlinejakby kompilator mógł zobaczyć swoją definicję w każdym pliku źródłowym, w którym gummoże zostać wywołany. Oznacza to, że jego definicja wbudowana musi istnieć w pliku nagłówkowym , który dołączasz do każdego kompilowanego pliku źródłowego, w którym gummoże zostać wywołany. Zrób jedną z dwóch rzeczy:

Albo nie wstawiaj definicji

Usuń inlinespecyfikator z definicji pliku źródłowego:

foo.cpp (2)

#include "foo.h"
#include <iostream>

void foo::bar() const {
    std::cout << __PRETTY_FUNCTION__ << std::endl;
}

gum.cpp (2)

#include "gum.h"
#include <iostream>

void gum()
{
    std::cout << __PRETTY_FUNCTION__ << std::endl;
}

Przebuduj z tym:

$ g++ -c  main.cpp foo.cpp gum.cpp
imk@imk-Inspiron-7559:~/develop/so/scrap1$ g++ -o prog main.o foo.o gum.o imk@imk-Inspiron-7559:~/develop/so/scrap1$ ./prog
void gum()
void foo::bar() const

Sukces.

Lub poprawnie wstawione

Definicje wbudowane w plikach nagłówkowych:

foo.h (2)

#pragma once
#include <iostream>

struct foo {
    void bar() const  { // In-class definition is implicitly inline
        std::cout << __PRETTY_FUNCTION__ << std::endl;
    }
};
// Alternatively...
#if 0
struct foo {
    void bar() const;
};
inline void foo::bar() const  {
    std::cout << __PRETTY_FUNCTION__ << std::endl;
}
#endif

gum.h (2)

#pragma once
#include <iostream>

inline void gum() {
    std::cout << __PRETTY_FUNCTION__ << std::endl;
}

Teraz nie potrzebujemy foo.cpplub gum.cpp:

$ g++ -c main.cpp $ g++ -o prog main.o
$ ./prog
void gum()
void foo::bar() const