Co to jest niezdefiniowany odwołanie / nierozwiązany błąd zewnętrznego symbolu i jak to naprawić?
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
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] .
- 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]
- 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]
- Plik źródłowy jest dekomponowany na tokeny przetwarzania wstępnego (2.5) i sekwencje znaków odstępu (w tym komentarze). [FANTASTYCZNA OKAZJA]
- Dyrektywy przetwarzania wstępnego są wykonywane, wywołania makr są rozwijane i wykonywane są wyrażenia operatora jednoargumentowego _Pragma. [FANTASTYCZNA OKAZJA]
- 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]
- Sąsiednie tokeny literału ciągu są konkatenowane.
- 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]
- Przetłumaczone jednostki tłumaczeniowe i jednostki instancji są połączone w następujący sposób: [SNIP]
- 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 a
w 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ą .lib
pliki. 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 LNK2019
dla 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 ++)
Członkowie klasy:
Czysty virtual
destruktor 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 virtual
metod 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;
};
virtual
Czł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, inline
aby 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() {}
static
czł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
const
ramach 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 const
członków danych.
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
libraryName
Tutaj jest tylko gołe nazwa biblioteki, bez platformy specyficznych dodatków. Na przykład w Linuksie pliki bibliotek są zwykle wywoływane, libfoo.so
ale 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 -l
lub -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 lib
plik zostanie wygenerowany (w powszechnym użyciu). Aby użyć symboli w oddzielnym projekcie, musisz uwzględnić lib
pliki w ustawieniach projektu. Odbywa się to w sekcji konsolidatora właściwości projektu w Input -> Additional Dependencies
. (ścieżkę do lib
pliku należy dodać Linker -> General -> Additional Library Directories
) W przypadku korzystania z biblioteki innej firmy, która jest dostarczana z lib
plikiem, 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”.
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.
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 A
zależy od biblioteki B
, to libA
MUSI pojawić się wcześniej libB
we 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!
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.
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"
}
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.
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_MODULE
był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
{
};
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 impl
pliku, 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::foo
do 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ź .
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:
- 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.
- 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.
niezdefiniowane odniesienie do WinMain@16
lub 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.
Również jeśli używasz bibliotek innych firm, upewnij się, że masz poprawne pliki binarne 32/64 bitowe
Firma Microsoft oferuje #pragma
moż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.
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 packages
folderze wewnątrz katalogu rozwiązania), znajdując packagename\build\native\packagename.targets
i wewnątrz tego pliku, kopiując wszystkie v110
sekcje. Zmieniłem v110
się v120
w 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.
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ć jak
error LNK2001: unresolved external symbol "void __cdecl foo(void)" (?foo@@YAXXZ)
Obserwacja
- Kiedy linker znajdzie jeden symbol, nie będzie szukał go w innych bibliotekach
- Kolejność łączenia bibliotek ma znaczenie .
- 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 once
do 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ń.
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.
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,--verbose
lub -v -Wl,-v
do 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 .
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.
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ć.
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
libfoo
zależy odlibbar
, to twoje połączenie jest poprawnie wstawianelibfoo
przedlibbar
. - 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
#include
d 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-config
poprawnie 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_lib
w 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.o
jest tylko jedna rzecz zdefiniowana w programie my_lib.o
, a mianowicie funkcja hw
.
Konsolidator zdecyduje, że twój program potrzebuje my_lib.o
wtedy 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.o
z biblioteki i doda ją do twojego programu. Następnie twój program zawiera definicję for hw
, więc jego odwołania do hw
zostaną 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.o
do programu i nie ma już zastosowania libmy_lib.a
.
Następnie znajduje eg1.o
i dodaje go jako program. Plik obiektowy w sekwencji łączenia jest zawsze dodawany do programu. Teraz program odwołuje się do hw
i nie zawiera definicji hw
; ale w sekwencji powiązań nie ma nic, co mogłoby dostarczyć brakującej definicji. Odniesienie do hw
pozostaje 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 main
funkcji, 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++
, gfortran
etc) 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 -lz
w sekwencji łączenia i /usr/lib/x86_64-linux-gnu/libz.so
zorientuje 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 libz
i 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ę
libz
do 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ęlibz
w 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-config
odmiana 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
-lfoo
w ich połączeniu rozwiążą się z/some/where/libfoo.a
lub 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.c
najpierw 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, gcc
zorientujesz 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.
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 -ltbb
do/home/foo/tbb-4.3/linux/lib/intel64/gcc4.4/libtbb.so.2
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 T
używanego w programie Foo
musi 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 Foo
typem, 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)
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.h
a skończysz używać go header2.h
we 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.lib
i 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 vec3
jest 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:
- Zwróć uwagę na dziwną zniekształconą nazwę podaną w błędzie konsolidatora. (np. rysuj @ grafika @ XYZ).
- Zrzuć wyeksportowane symbole z biblioteki do pliku tekstowego.
- Wyszukaj wyeksportowany symbol zainteresowania i zwróć uwagę, że zniekształcona nazwa jest inna.
- 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.
- 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!
Niespójne UNICODE
definicje
Kompilacja Windows UNICODE jest budowana z TCHAR
itp. Zdefiniowanymi jako wchar_t
itp. Gdy nie buduje się z UNICODE
definicją jako kompilacja z TCHAR
definicją jako char
itp. Te UNICODE
i _UNICODE
definicje wpływają na wszystkie typy łańcuchów " T" ; LPTSTR
, LPCTSTR
I ich Ełku.
Zbudowanie jednej biblioteki ze UNICODE
zdefiniowaną i próba połączenia jej w projekcie, w którym UNICODE
nie jest zdefiniowana, spowoduje błędy linkera, ponieważ wystąpi niezgodność w definicji TCHAR
; char
vs wchar_t
.
Błąd zwykle obejmuje funkcję, wartość z typem pochodnym char
lub 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 TCHAR
lub 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
).
Można to zrobić za pomocą jednego z nich;
#define UNICODE #define _UNICODE
Lub w ustawieniach projektu;
Właściwości projektu> Ogólne> Wartości domyślne projektu> Zestaw znaków
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”.
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).
Brak „extern” w const
deklaracjach / definicjach zmiennych (tylko C ++)
Dla osób wywodzących się z C może być zaskoczeniem, że w C ++ const
zmienne globalne mają wewnętrzne (lub statyczne) powiązanie. W C tak się nie stało, ponieważ wszystkie zmienne globalne są niejawnie extern
(tj. Gdy static
brakuje 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ć const
zmienną w pliku1.cpp z jawnym użyciemextern
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::path
ale 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> .
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 nm
z małymi literami, na przykład t
zamiast `T dla sekcji kodu:
nm XXX.so
00000000000005a7 t HIDDEN_SYMBOL
00000000000005f8 T VISIBLE_SYMBOL
Możesz także użyć nm
z opcją -C
do rozszyfrowania nazw (jeśli użyto C ++).
Podobnie jak w Windows-dll, można by oznaczyć funkcje publiczne definicją, na przykład DLL_PUBLIC
zdefiniowaną 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=hidden
wynikowymi 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
Funkcje lub metody klas są definiowane w plikach źródłowych ze inline
specyfikatorem.
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 inline
w 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
gum
wbudowanymi kopiami skompilowanej treścigum
.
W rezultacie, jeśli zdefiniujesz gum
inline w pliku źródłowym gum.cpp
, jest on kompilowany do pliku obiektowego, gum.o
w którym wszystkie wywołania gum
są wbudowane i nie jest zdefiniowany żaden symbol, do którego może się odwoływać konsolidator gum
. Kiedy łączysz gum.o
się z programem razem z innym plikiem obiektowym, np. main.o
Któ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 gum
tak, inline
jakby kompilator mógł zobaczyć swoją definicję w każdym pliku źródłowym, w którym gum
moż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 gum
może zostać wywołany. Zrób jedną z dwóch rzeczy:
Albo nie wstawiaj definicji
Usuń inline
specyfikator 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.cpp
lub gum.cpp
:
$ g++ -c main.cpp $ g++ -o prog main.o
$ ./prog
void gum()
void foo::bar() const