Co to jest nieokreślony błąd odniesienia / nierozwiązany symbol zewnętrzny i jak go naprawić?

1493

Co to są niezdefiniowane odniesienia / nierozwiązane błędy symboli zewnętrznych? Jakie są typowe przyczyny i jak je naprawić / zapobiec?

Możesz edytować / dodawać własne.

Luchian Grigore
źródło
@LuchianGrigore ”dodaj odpowiedź„ Wolę dodać odpowiedni link (IMHO) jako podstawową odpowiedź, jeśli chcesz na to pozwolić.
πάντα ῥεῖ
19
To pytanie jest zbyt ogólne, aby przyznać przydatną odpowiedź. Nie mogę uwierzyć, że ponad 1000 osób, które komentują ten temat, nie straci pytania. W międzyczasie widzę wiele pytań, które są dla mnie sensowne i bardzo przydatne, które są zamknięte.
Albert van der Horst
10
@AlbertvanderHorst „To pytanie jest zbyt ogólne, aby przyznać przydatną odpowiedź” Czy poniższe odpowiedzi nie są przydatne?
LF
4
Mogą być przydatne, podobnie jak wiele odpowiedzi na pytania oznaczone jako zbyt ogólne.
Albert van der Horst
1
Szczerze mówiąc, chciałbym zobaczyć minimalny odtwarzalny przykład jako coś, o co prosimy większość nowych użytkowników. Nic przez to nie rozumiem, po prostu - nie możemy oczekiwać, że ludzie będą przestrzegać zasad, których sami sobie nie wymuszamy.
Danilo,

Odpowiedzi:

848

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

Pierwszeństwo wśród reguł składni tłumaczenia są określone przez następujące fazy [patrz przypis] .

  1. Znaki fizycznego pliku źródłowego są odwzorowywane, w sposób zdefiniowany w implementacji, na podstawowy zestaw znaków źródłowych (wprowadzając znaki nowej linii dla wskaźników końca linii), jeśli to konieczne. [FANTASTYCZNA OKAZJA]
  2. Każde wystąpienie znaku odwrotnego ukośnika (\), po którym bezpośrednio następuje znak nowej linii, jest usuwane, łącząc fizyczne linie źródłowe, tworząc logiczne linie źródłowe. [FANTASTYCZNA OKAZJA]
  3. Plik źródłowy jest rozkładany na tokeny przetwarzania wstępnego (2.5) i sekwencje znaków białych znaków (w tym komentarze). [FANTASTYCZNA OKAZJA]
  4. Wykonywane są dyrektywy przetwarzania wstępnego, rozwijane są makropolecenia i wykonywane są wyrażenia jednoargumentowe _Pragma. [FANTASTYCZNA OKAZJA]
  5. Każdy źródłowy element zestawu znaków w literałach znaków lub literałach ciągów, a także każda sekwencja ucieczki i nazwa-znaków uniwersalnych w literałach znaków lub literałach łańcuchowych nieobrobionych jest konwertowany na odpowiedni element zestawu znaków wykonania; [FANTASTYCZNA OKAZJA]
  6. Sąsiednie ciągi literałów literowych są łączone.
  7. Znaki oddzielające białe znaki nie są już znaczące. Każdy token przetwarzania wstępnego jest konwertowany na token. (2.7). Otrzymane tokeny są analizowane składniowo i semantycznie oraz tłumaczone jako jednostka tłumacząca. [FANTASTYCZNA OKAZJA]
  8. Przetłumaczone jednostki tłumaczenia i jednostki tworzenia instancji są łączone w następujący sposób: [SNIP]
  9. Wszystkie odwołania do encji zewnętrznych zostały rozwiązane. Komponenty biblioteki są połączone w celu spełnienia zewnętrznych odniesień do encji, które nie zostały zdefiniowane w bieżącym tłumaczeniu. Wszystkie takie dane wyjściowe translatora są gromadzone w obrazie programu, który zawiera informacje potrzebne do wykonania w środowisku wykonawczym. (moje podkreślenie)

[przypis] Implementacje muszą zachowywać się tak, jakby miały miejsce te oddzielne fazy, chociaż w praktyce różne fazy mogą być składane razem.

Określone błędy występują podczas ostatniego etapu kompilacji, najczęściej nazywanego łączeniem. Zasadniczo oznacza to, że skompilowałeś kilka plików implementacyjnych w plikach obiektowych lub bibliotekach i teraz chcesz je ze sobą współpracować.

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

Jeśli korzystasz z Microsoft Visual Studio, zobaczysz, że projekty generują .libpliki. Zawierają one tabelę eksportowanych symboli i tabelę importowanych symboli. Zaimportowane symbole są rozpoznawane względem bibliotek, z którymi się łączysz, a wyeksportowane symbole są dostarczane dla bibliotek, które z nich korzystają .lib(jeśli 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

Najczęstsze przyczyny to:

Luchian Grigore
źródło
16
Osobiście uważam, że komunikaty o błędach linkera MS są tak samo czytelne jak błędy GCC. Mają także tę zaletę, że zawierają zarówno zniekształcone, jak i nieukończone nazwy dla nierozwiązanego zewnętrznego. Posiadanie zniekształconej nazwy może być pomocne, gdy trzeba spojrzeć bezpośrednio na biblioteki lub pliki obiektów, aby zobaczyć, na czym polega problem (na przykład niezgodność konwencji wywoływania). Poza tym nie jestem pewien, która wersja MSVC spowodowała błędy tutaj, ale nowsze wersje zawierają nazwę (zarówno zniekształconą, jak i niezmienioną) funkcji odnoszącej się do nierozwiązanego symbolu zewnętrznego.
Michael Burr,
5
David Drysdale napisał świetny artykuł o tym, jak działają linkery: Przewodnik dla linkerów dla początkujących . Biorąc pod uwagę temat tego pytania, pomyślałem, że może się przydać.
Pressacco
@TankorSmash Używasz gcc? Dokładniej MinGW.
doug65536,
1
@luchian byłoby miło, jeśli dodasz
poprawkę
177

Członkowie klasy:

Czysty virtualdestruktor wymaga implementacji.

Zadeklarowanie czystego destruktora nadal wymaga jego zdefiniowania (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ż niszczyciele klasy podstawowej są wywoływane, gdy obiekt jest niszczony niejawnie, dlatego wymagana jest definicja.

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

Jest to podobne do virtualmetod niebędących metodami bez definicji, z dodanym uzasadnieniem, że czysta deklaracja generuje fikcyjny vtable i możesz otrzymać błąd linkera 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 spoza klasy

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

struct A
{ 
    ~A();
};

Poniższe spowodowałoby 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 jest poza definicją klasy, ale w nagłówku, metody należy oznaczyć jako inlinezapobiegające wielokrotnej definicji.

Wszystkie używane metody składowe muszą zostać zdefiniowane, jeśli są używane.

Częstym błędem jest zapominanie o określeniu nazwy:

struct A
{
   void foo();
};

void foo() {}

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

Definicja powinna być

void A::foo() {}

staticelementy danych muszą być zdefiniowane 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

Inicjator może być zapewniony dla elementu static constdanych typu integralnego lub wyliczeniowego w ramach definicji klasy; jednak użycie odr tego elementu nadal będzie 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.

Luchian Grigore
źródło
Pomyślałem, że możesz podkreślić, że robienie obu jest możliwe, a dtor nie jest tak naprawdę wyjątkiem. (na pierwszy rzut oka nie jest to oczywiste).
Deduplicator
112

Brak połączenia z odpowiednimi bibliotekami / plikami obiektowymi lub kompilacją plików implementacyjnych

Zwykle każda jednostka tłumaczeniowa 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 należy podać wszystkie pliki obiektów, które mają zostać połączone w wierszu poleceń, lub skompilować pliki implementacji razem.

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

libraryNameTutaj jest tylko gołe nazwa biblioteki, bez platformy specyficznych dodatków. Tak np. W Linuksie pliki bibliotek są zwykle wywoływane, libfoo.soale piszesz tylko -lfoo. W systemie Windows ten sam plik może zostać wywołany foo.lib, ale użyłbyś tego samego argumentu. Może być konieczne dodanie katalogu, w którym można znaleźć te pliki -L‹directory›. Pamiętaj, aby nie pisać spacji po -llub -L.

W przypadku XCode : dodaj ścieżki wyszukiwania nagłówka użytkownika -> dodaj ścieżkę wyszukiwania biblioteki -> przeciągnij i upuść aktualne odwołanie do biblioteki w folderze projektu.

W MSVS pliki dodane do projektu automatycznie łączą swoje pliki obiektowe i libplik zostałby wygenerowany (w powszechnym użyciu). Aby użyć symboli w osobnym projekcie, musisz uwzględnić libpliki w ustawieniach projektu. Odbywa się to w sekcji Linker właściwości projektu, w Input -> Additional Dependencies. ( libnależy dodać ścieżkę do pliku Linker -> General -> Additional Library Directories) W przypadku korzystania z biblioteki innej firmy, która jest dostarczana z libplikiem, nieprzestrzeganie tego zwykle powoduje błąd.

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

W programowaniu Windows znakiem ostrzegawczym, że nie podłączyłeś niezbędnej biblioteki, jest to, że nazwa nierozwiązanego symbolu zaczyna się od __imp_. Wyszukaj nazwę funkcji w dokumentacji i powinna ona wskazywać, której biblioteki należy użyć. Na przykład MSDN umieszcza informacje w polu u dołu każdej funkcji w sekcji o nazwie „Biblioteka”.

Luchian Grigore
źródło
1
Byłoby dobrze, gdybyś mógł jawnie omówić typowy błąd gcc main.czamiast gcc main.c other.c (co często robią początkujący, zanim ich projekty stają się tak duże, że można zbudować pliki .o).
MM
101

Zadeklarowano, ale nie zdefiniowano zmiennej lub funkcji.

Typowa deklaracja zmiennej to

extern int x;

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

int x;

Na przykład następujące wygenerował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 była dokładnie taka sama, jak zadeklarowana. Na przykład możesz mieć niedopasowane 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 niedopasowań obejmują

  • Funkcja / zmienna zadeklarowana w jednej przestrzeni nazw, zdefiniowanej w innej.
  • Funkcja / zmienna zadeklarowana jako członek klasy, zdefiniowana jako globalna (lub odwrotnie).
  • Typ zwracanej funkcji, numer i typ parametru oraz konwencja wywoływania nie są dokładnie zgodne.

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

Luchian Grigore
źródło
W VS pliki CPP pasujące do plików w nagłówku, które #includesnie zostały dodane do katalogu źródłowego, również należą do kategorii brakujących definicji.
Laurie Stearn
86

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

Kolejność łączenia bibliotek NIE MA znaczenia, jeśli biblioteki są od siebie zależne. Ogólnie, jeśli biblioteka Azależy od biblioteki B, libA MUSI pojawić się wcześniej libBw flagach linkera.

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ść CZY sprawa!

Svalorzen
źródło
Ciekawostką jest fakt, że w moim przypadku miałem jeden plik obiektowy, który zależy od wspólnej biblioteki. Musiałem zmodyfikować plik Makefile i umieścić bibliotekę PO obiekcie z gcc 4.8.4 na Debianie. Na Centos 6.5 z gcc 4.4 Makefile działał bez problemu.
Marco Sulla,
74

co to jest „niezdefiniowany odnośnik / nierozwiązany symbol zewnętrzny”

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

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

Na przykład mamy trochę kodu

// 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 dowolne 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 danych wyjściowych, ponieważ nie mają one znaczenia

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

[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 to

$ ./prog
123

Linker widzi wyeksportowane symbole i łączy je. Teraz próbujemy odkomentować wiersze w src2.cpp 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, łączenie jeszcze się nie zakończyło. 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 local_var_name jest statyczna, tzn. Nie jest widoczna dla innych modułów. Teraz głębiej. Uzyskaj wynik fazy tłumaczenia

$ 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 linker go nie znalazł. Ale 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

tzn. 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 jego wartość na 456789. Spróbuj zbudować z niego plik obiektowy

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

ok, patrz readelf wyjście (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 to

$ ./prog 
123456789

ok, zhakujemy to :)

W rezultacie - „niezdefiniowany błąd odniesienia / nierozwiązany błąd symbolu zewnętrznego” ma miejsce, gdy linker nie może znaleźć symboli globalnych w plikach obiektów.

Kastaneda
źródło
71

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

Linker C ++ oczekuje, że nazwy będą zniekształcone, więc musisz zadeklarować funkcję jako:

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

Odpowiednio, zamiast być zdefiniowanym w programie C, funkcja (lub zmienna) void foo()została zdefiniowana w C ++, ale z połączeniem C:

extern "C" void foo();

i próbujesz użyć go w programie C ++ z łączeniem 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"
}
Luchian Grigore
źródło
5
Albo odwrotnie, jeśli rozwijać biblioteki C, reguła miło jest ochrona nagłówka pliku (ów) otaczając wywożone z deklaracji #ifdef __cplusplus [\n] extern"C" { [\n] #endifi #ifdef __cplusplus [\n] } [\n] #endif( [\n]jest prawdziwy powrót karetki, ale nie mogę napisać to poprawnie w komentarzu).
Bentoy13,
Jak w powyższym komentarzu, sekcja „Tworzenie nagłówków mieszanych” pomogła tutaj: oracle.com/technetwork/articles/servers-storage-dev/…
zanbri
Naprawdę mi pomógł! To był mój przypadek, kiedy szukałem tego pytania
knightofhorizon
To również może się zdarzyć, jeśli zawierać zwykły plik nagłówka C ++ przez przypadek otoczony extern C : extern "C" { #include <myCppHeader.h> }.
ComFreek
68

Jeśli wszystko inne zawiedzie, skompiluj ponownie.

Niedawno udało mi się pozbyć nierozwiązanego błędu zewnętrznego w Visual Studio 2012, po prostu przez przekompilowanie niepoprawnego pliku. Po ponownej kompilacji błąd zniknął.

Zwykle dzieje się tak, gdy dwie (lub więcej) bibliotek mają cykliczną zależność. Biblioteka A próbuje użyć symboli w B.lib, a biblioteka B próbuje użyć symboli z A.lib. Żaden nie istnieje na początek. Podczas próby skompilowania A krok połączenia nie powiedzie się, ponieważ nie można znaleźć B.lib. A.lib zostanie wygenerowany, ale nie będzie dll. Następnie skompilujesz B, który zakończy się sukcesem i wygeneruje B.lib. Ponowna kompilacja A będzie teraz działać, ponieważ znaleziono B.lib.

sgryzko
źródło
1
Prawidłowo - dzieje się tak, gdy biblioteki mają cykliczną zależność.
Luchian Grigore,
1
Rozszerzyłem twoją odpowiedź i włączyłem w głównej. Dzięki.
Luchian Grigore,
57

Niepoprawne importowanie / eksportowanie metod / klas w modułach / dll (specyficzne dla kompilatora).

MSVS wymaga określenia, które symbole mają być eksportowane i importowane za pomocą __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, który eksportuje funkcję. W ten sposób deklaracja:

DLLIMPEXP void foo();

rozwija się do

__declspec(dllexport) void foo();

i każe kompilatorowi wyeksportować funkcję, ponieważ bieżący moduł zawiera jej definicję. Uwzględniając deklarację w innym module, rozwinąłby 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 podobnie importować / eksportować klasy:

class DLLIMPEXP X
{
};
Luchian Grigore
źródło
2
Aby być kompletnym, w odpowiedzi należy wspomnieć o plikach GCC visibilityi Windows .def, ponieważ mają one również wpływ na nazwę i obecność symbolu.
rubenvb
@rubenvb Nie używałem .defplików od wieków. Dodaj odpowiedź lub edytuj tę odpowiedź.
Luchian Grigore,
57

Jest to jeden z najbardziej mylących komunikatów o błędach, które wszyscy programiści VC ++ widzieli od czasu do czasu. Najpierw sprawmy, żeby wszystko było jasne.

A. Co to jest symbol? Krótko mówiąc, symbol to nazwa. Może to być nazwa zmiennej, nazwa funkcji, nazwa klasy, nazwa typedef lub cokolwiek innego niż te nazwy i znaki, które należą do języka C ++. Jest zdefiniowany lub wprowadzony przez bibliotekę zależności (inny zdefiniowany przez użytkownika).

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

C. Co to jest „postanowienie”? Resolve to termin łączenia. W czasie łączenia linker próbuje znaleźć definicję zewnętrzną dla każdego symbolu w plikach obiektowych, który nie może znaleźć swojej definicji wewnętrznie. Zakres tego procesu wyszukiwania obejmuje:

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

Ten proces wyszukiwania nazywa się rozwiązać.

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

E. Możliwe przyczyny LNK2019 : Nierozwiązany błąd symbolu zewnętrznego. Wiemy już, że ten błąd wynika z faktu, ż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 pliku 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 innym ciele funkcji, powiedz bar():

void bar()
{
    foo();
}

Teraz, kiedy skompilujesz ten kod, pojawi się 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 definicji.

  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 od Project | Properties | Configuration Properties | Linker | Input | Additional Dependency:) w ustawieniach projektu. Teraz linker zgłosi LNK2019, ponieważ definicja nie istnieje w bieżącym zakresie wyszukiwania.

Nima Soroush
źródło
1
Dziękuję za systematyczne wyjaśnienie. Po przeczytaniu tego mogłem zrozumieć inne odpowiedzi tutaj.
displayName
55

Implementacje szablonu nie są widoczne.

Nieokreślone szablony muszą mieć swoje definicje widoczne dla wszystkich jednostek tłumaczeniowych, które ich używają. Oznacza to, że nie można oddzielić definicji szablonu do pliku implementacji. Jeśli musisz oddzielić implementację, zwykłym obejściem jest posiadanie implpliku, który dołączasz na końcu nagłówka, który deklaruje szablon. Częstą sytuacją jest:

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łówka lub innego miejsca widocznego dla używającej go jednostki tłumaczeniowej.

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

W celu uzyskania dalszych wyjaśnień i innego możliwego rozwiązania (wyraźna instancja) zobacz to pytanie i odpowiedź .

Luchian Grigore
źródło
1
Więcej informacji na temat „szablonów musi być zdefiniowanych w nagłówku” można znaleźć na stackoverflow.com/questions/495021
PlasmaHH
41

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

Możliwe, że nie wybrałeś odpowiedniego typu projektu z faktycznym 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 Windows.


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

πάντα ῥεῖ
źródło
2
Nie można nie wspomnieć o tym pytaniu oraz o tym, że jest to częściej spowodowane brakiem żadnej głównej funkcji niż brakiem WinMain. Prawidłowe programy C ++ wymagają main.
Chris
36

Również jeśli korzystasz z bibliotek stron trzecich, upewnij się, że masz poprawne 32-bitowe pliki binarne

Dula
źródło
34

Microsoft oferuje #pragmaodwołanie do właściwej biblioteki w czasie łącza;

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

Oprócz ścieżki do biblioteki, w tym katalogu biblioteki, powinna to być pełna nazwa biblioteki.

Niall
źródło
34

Pakiet Visual Studio NuGet wymaga aktualizacji do nowej wersji zestawu narzędzi

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

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

Zredagowałem pakiet (w packagesfolderze w 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 Visual Studio 2013 połączyć się z bibliotekami na 2012 rok, w tym przypadku zadziałało.

Malvineous
źródło
1
Wydaje się to zbyt szczegółowe - być może nowy wątek byłby lepszym miejscem dla tej odpowiedzi.
Luchian Grigore,
3
@LuchianGrigore: Chciałem opublikować tutaj, ponieważ to pytanie było dokładnie tym problemem, ale zostało oznaczone jako duplikat tego pytania, więc nie mogłem tam odpowiedzieć. Zamiast tego opublikowałem tutaj swoją odpowiedź.
Malvineous
To pytanie ma już zaakceptowaną odpowiedź. Jest oznaczony jako duplikat, ponieważ ogólna przyczyna jest wymieniona powyżej. Co by się stało, gdybyśmy mieli odpowiedź na każdą problem z biblioteką, która nie została uwzględniona?
Luchian Grigore,
6
@LuchianGrigore: Ten problem nie jest specyficzny dla biblioteki, dotyczy wszystkich bibliotek korzystających z systemu zarządzania pakietami Visual Studio. Właśnie znalazłem inne pytanie, ponieważ oboje mieliśmy problemy z libpng. Miałem również ten sam problem (z tym samym rozwiązaniem) dla libxml2, libiconv i glew. To pytanie dotyczy problemu z systemem zarządzania pakietami Visual Studio, a moja odpowiedź wyjaśnia przyczynę i zapewnia obejście. Ktoś właśnie widział „nierozwiązany zewnętrzny” i założył, że jest to standardowy problem z linkerem, podczas gdy w rzeczywistości jest to problem z zarządzaniem pakietami.
Malvineous,
34

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

Jakie jest znaczenie kompilacji?

  • Visual Studio przeszuka plik .vcxproj i zacznie kompilować każdy plik, który ma rozszerzenie .cpp. Kolejność kompilacji jest niezdefiniowana, więc nie można zakładać, że plik main.cpp jest najpierw kompilowany
  • 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 generowany jest plik obiektowy .o, a także Visual Studio zapisuje dane wyjściowe w pliku o nazwie ProjectName.Cpp.Clean.txt, który zawiera wszystkie pliki obiektowe, które muszą zostać przetworzone przez linker.

Drugi krok kompilacji wykonuje Linker. Linker powinien scalić cały plik obiektowy i w końcu zbudować wynik (który może być plikiem wykonywalnym lub biblioteką)

Kroki w łączeniu projektu

  • Analizuj wszystkie pliki obiektowe i znajdź definicję zadeklarowaną tylko w nagłówkach (np .: Kod jednej metody klasy, jak wspomniano w poprzednich odpowiedziach, lub zdarzenie inicjalizacji zmiennej statycznej, która jest członkiem klasy)
  • Jeśli nie można znaleźć jednego symbolu w plikach obiektowych, jest on również przeszukiwany w dodatkowych bibliotekach. W celu dodania nowej biblioteki do projektu Właściwości konfiguracji -> Katalogi VC ++ -> Katalogi bibliotek i tutaj określono dodatkowy folder do przeszukiwania bibliotek i właściwości konfiguracji -> Linker -> Dane wejściowe do określania nazwy biblioteki. -Jeśli Linker nie może znaleźć symbolu, który piszesz w jednym .cpp, podnosi błąd czasu linkera, który może brzmieć jak error LNK2001: unresolved external symbol "void __cdecl foo(void)" (?foo@@YAXXZ)

Obserwacja

  1. Gdy Linker znajdzie jeden symbol, nie szuka go w innych bibliotekach
  2. Kolejność łączenia bibliotek ma znaczenie .
  3. Jeśli Linker znajdzie zewnętrzny symbol w jednej bibliotece statycznej, dołącza ten symbol do wyniku projektu, jednak jeśli biblioteka jest współdzielona (dynamiczna), nie dołącza kodu (symboli) do wyjścia, ale awarie w czasie wykonywania mogą pojawić się

Jak rozwiązać ten rodzaj błędu

Błąd czasu kompilatora:

  • Upewnij się, że poprawnie napisałeś swój projekt c ++.

Błąd czasu łącznika

  • Zdefiniuj cały swój symbol, który zadeklarujesz w plikach nagłówkowych
  • Służy #pragma oncedo zezwalania kompilatorowi na niewłączanie jednego nagłówka, jeśli był już zawarty w bieżącym .cpp, które są kompilowane
  • Upewnij się, że twoja biblioteka zewnętrzna nie zawiera symboli, które mogą wchodzić w konflikt z innymi symbolami zdefiniowanymi w plikach nagłówka
  • Gdy używasz szablonu, aby upewnić się, że dołączasz definicję każdej funkcji szablonu w pliku nagłówka, aby umożliwić kompilatorowi wygenerowanie odpowiedniego kodu dla dowolnych instancji.
eFarzad
źródło
Czy twoja odpowiedź nie jest specyficzna dla studia wizualnego? Pytanie nie określa żadnych narzędzi IDE / kompilatora, więc sprawia, że ​​twoja odpowiedź jest bezużyteczna w przypadku części innej niż wizualna.
Victor Polevoy,
Masz rację . Ale każdy proces kompilacji / łączenia IDE odbywa się nieco inaczej, ale pliki są przetwarzane dokładnie tak samo (nawet g ++ robi to samo, kiedy analizuje flagi ..)
Problem nie dotyczy w rzeczywistości IDE, ale odpowiedzi na problemy z łączeniem. Problemy z łączeniem nie są związane z IDE, ale z kompilatorem i procesem kompilacji.
Victor Polevoy,
Tak, ale proces kompilacji / łączenia odbywa się w g ++ / Visual Studio (kompilator dostarczony przez Microsoft dla VS) / Eclipse / Net Beans w ten sam sposób
29

Błąd w kompilatorze / IDE

Niedawno miałem ten problem i okazało się, że był to błąd w Visual Studio Express 2013 . Musiałem usunąć plik źródłowy z projektu i ponownie go dodać, aby usunąć 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ą taką opcję, możesz to również zrobić ręcznie, usuwając pliki obiektów)
  • Spróbuj rozpocząć nowy projekt, kopiując cały kod źródłowy z oryginalnego.
developerbmw
źródło
5
Wiara w to, że twoje narzędzia są zepsute, najprawdopodobniej odciągnie cię od prawdziwej przyczyny. Jest o wiele bardziej prawdopodobne, że popełniłeś błąd, niż kompilator spowodował twój problem. Czyszczenie rozwiązania lub ponowne tworzenie konfiguracji kompilacji może naprawić błędy kompilacji, ale to nie znaczy, że w kompilatorze występuje błąd. Połączony link „okazało się, że to błąd” nie został potwierdzony przez Microsoft i nie jest powtarzalny.
JDiMatteo
4
@JDiMatteo Istnieje 21 odpowiedzi na to pytanie, dlatego znaczna liczba odpowiedzi nie będzie „prawdopodobnym” rozwiązaniem. Jeśli odrzucisz wszystkie odpowiedzi poniżej Twojego progu prawdopodobieństwa, ta strona stanie się bezużyteczna, ponieważ większość typowych przypadków jest łatwo zauważalna.
developerbmw
27

Użyj łącznika, aby zdiagnozować błąd

Większość współczesnych linkerów zawiera pełną opcję, która drukuje w różnym stopniu;

  • Wywołanie linku (linia poleceń),
  • Dane o bibliotekach uwzględnionych na etapie łączenia,
  • Lokalizacja bibliotek,
  • Używane ścieżki wyszukiwania.

Dla gcc i clang; zazwyczaj dodajesz -v -Wl,--verboselub -v -Wl,-vdo linii poleceń. Więcej informacji można znaleźć tutaj;

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

Niall
źródło
26

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

Miałem ten sam problem. Powiedz, że mam projekty MyProject i TestProject. Skutecznie połączyłem plik lib MyProject z TestProject. Ten plik lib został jednak utworzony, ponieważ zbudowano bibliotekę 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 biblioteki DLL.

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

Kiriloff
źródło
25

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

Jednym z możliwych powodów błędów linkera w GCC 5.2.0 jest to, że nowa ABI biblioteki libstdc ++ jest teraz domyślnie wybierana.

Jeśli pojawią się błędy linkera dotyczące niezdefiniowanych odwołań do symboli, które dotyczą typów w przestrzeni nazw std :: __ cxx11 lub znaczniku [abi: cxx11], oznacza to prawdopodobnie, że próbujesz połączyć ze sobą pliki obiektów, które zostały skompilowane z różnymi wartościami dla _GLIBCXX_USE_CXX11_ABI makro. Zdarza się to często podczas łączenia się z biblioteką innej firmy, która została skompilowana ze starszą wersją GCC. Jeśli biblioteki innej firmy nie można odbudować przy użyciu nowego ABI, należy ponownie skompilować kod ze starym ABI.

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

Plankalkül
źródło
20

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

Niektóre pliki .so są w rzeczywistości skryptami konsolidatora GNU ld , np. Plik libtbb.so to plik tekstowy ASCII o następującej treści:

INPUT (libtbb.so.2)

Niektóre bardziej złożone kompilacje mogą tego nie obsługiwać. Na przykład, jeśli dodasz -v do opcji kompilatora, zobaczysz, że mwdip otoki mainwin gcc odrzuca pliki poleceń skryptu linkera na pełnej liście wyjściowej bibliotek, do których ma się połączyć. Prostym obejściem jest zastąpienie polecenia wejściowego skryptu linkera plik z kopią pliku zamiast (lub dowiązaniem symbolicznym), np

cp libtbb.so.2 libtbb.so

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

JDiMatteo
źródło
20

Twoje połączenie zużywa biblioteki przed plikami obiektowymi, które się do nich odnoszą

  • Próbujesz skompilować i połączyć swój program z zestawem narzędzi GCC.
  • Twój link określa wszystkie niezbędne biblioteki i ścieżki wyszukiwania bibliotek
  • Jeśli libfoozależy od libbartego, twój link poprawnie wstawia libfoowcześniej libbar.
  • Twoje połączenie zawiedzie z undefined reference to czymś błędami.
  • Ale wszystkie nieokreślone coś s deklarowane są w plikach nagłówkowych masz #includeD i faktycznie są zdefiniowane w bibliotekach, które są linkami.

Przykłady znajdują się w C. Równie dobrze mogłyby to być C ++

Minimalny przykład dotyczący biblioteki statycznej, którą sam zbudowałeś

my_lib.c

#include "my_lib.h"
#include <stdio.h>

void hw(void)
{
    puts("Hello World");
}

my_lib.h

#ifndef MY_LIB_H
#define MT_LIB_H

extern void hw(void);

#endif

eg1.c

#include <my_lib.h>

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

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 połączyć go z libmy_lib.ai nie powiedzie się:

$ 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ólnej 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 libzi zawieść:

$ 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 wariant na przykładzie 2 obejmujący 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ć w celu utworzenia programu, umieszczasz biblioteki przed plikami obiektowymi, które się do nich odnoszą. Musisz umieścić biblioteki po plikach obiektowych, które się do nich odnoszą.

Podłącz przykład 1 poprawnie:

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

Sukces:

$ ./eg1 
Hello World

Podłącz przykład 2 poprawnie:

$ gcc -o eg2 eg2.o -lz

Sukces:

$ ./eg2 
1.2.8

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

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

Wyjaśnienie

Odczytywanie jest odtąd opcjonalne .

Domyślnie polecenie powiązania wygenerowane przez GCC w twojej dystrybucji zużywa pliki w powiązaniu od lewej do prawej w sekwencji wiersza poleceń. Gdy stwierdzi, że plik odnosi się do czegoś i nie zawiera definicji tego, szuka definicji w plikach po prawej stronie. Jeśli w końcu znajdzie definicję, odwołanie zostanie rozwiązane. Jeśli na końcu jakieś odniesienia pozostaną nierozwiązane, połączenie się nie powiedzie: linker nie szuka wstecz.

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

Biblioteka statyczna to zindeksowane archiwum plików obiektowych. Gdy linker znajdzie -lmy_libsekwencję łączenia i ./libmy_lib.azorientuje się, że odnosi się to do biblioteki statycznej , chce wiedzieć, czy twój program potrzebuje plików obiektowych libmy_lib.a.

Jest tylko plik obiektowy libmy_lib.a, a mianowicie my_lib.o, i jest zdefiniowana tylko jedna rzecz my_lib.o, a mianowicie funkcja hw.

Linker zdecyduje, że twój program potrzebuje my_lib.owtedy i tylko wtedy, gdy już wie, że twój program się odwołuje hw, w co najmniej jednym pliku obiektowym, który już dodał do programu, i że żaden z plików obiektowych, który 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ę hw, więc jego odniesienia do hwzostały rozwiązane .

Podczas próby połączenia programu, takiego jak:

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

linker nie dodał eg1.o do programu, gdy widzi -lmy_lib. Ponieważ w tym momencie nie widziałem 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.

Linker nie dodaje my_lib.osię do programu i nie ma dalszego zastosowania libmy_lib.a.

Następnie znajduje eg1.oi dodaje do programu. Plik obiektowy w sekwencji łączenia jest zawsze dodawany do programu. Teraz program zawiera odniesienie hwi nie zawiera definicji hw; ale w sekwencji łączenia nie pozostało nic, co mogłoby zapewnić brakującą definicję. Odwołanie do hwkończy się nierozwiązane , a powiązanie kończy się niepowodzeniem.

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

Biblioteka współdzielona nie jest archiwum plików obiektowych ani nic podobnego. Bardziej przypomina program , który nie ma mainfunkcji i zamiast tego wyświetla wiele innych symboli, które definiuje, aby inne programy mogły z nich korzystać 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 gdy linker znajdzie -lzsekwencję łą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 dodane do programu, 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 dokona kodu twojego programu, aby:

  • W czasie wykonywania program ł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, ilekroć twój program odwołuje się do czegoś, co jest zdefiniowane w libz, to odwołanie korzysta z definicji wyeksportowanej przez kopię libzw tym samym procesie.

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

Ale kiedy próbujesz połączyć program w następujący sposób:

gcc -o eg2 -lz eg2.o

kolejność zdarzeń jest niepoprawna 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. Linker decyduje więc, że nie ma sensu libz. Kiedy osiągnie eg2.o, dodaje go do programu, a następnie ma niezdefiniowane odniesienie zlibVersion, sekwencja łączenia jest zakończona; to odniesienie jest nierozwiązane, a połączenie nie działa.

Wreszcie pkg-configodmiana z przykładu 2 ma teraz oczywiste wytłumaczenie. Po rozszerzeniu powłoki:

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

staje się:

gcc -o eg2 -lz eg2.o

co jest znowu 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)

Wynika to z faktu, że dystrybucja, w której działa łączenie, jest jedną z tych, które nie konfigurują łańcucha narzędzi GCC do łączenia bibliotek współdzielonych w razie potrzeby .

W tamtych czasach normalne dla systemów uniksowych było łączenie bibliotek statycznych i współdzielonych według różnych reguł. Biblioteki statyczne w sekwencji łączenia zostały połączone zgodnie z potrzebami wyjaśnionymi w przykładzie 1, ale biblioteki wspólne zostały połączone bezwarunkowo.

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

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

  • 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 powiązaniu rozwiążą się /some/where/libfoo.alub nie /some/where/libfoo.so, i mogą nie rozumieć różnicy między bibliotekami współdzielonymi a statycznymi.

Ten kompromis doprowadził dziś do schizmatyckiej sytuacji. Niektóre dystrybucje zmieniły swoje reguły łączenia GCC dla bibliotek współużytkowanych, tak aby zasada według potrzeb miała zastosowanie do wszystkich bibliotek. Niektóre dystrybucje utknęły w starym stylu.

Dlaczego wciąż mam ten problem, nawet jeśli jednocześnie kompiluję i łączę?

Jeśli tylko to zrobię:

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

z pewnością gcc eg1.cnajpierw musi się skompilować , a następnie połączyć wynikowy plik obiektowy libmy_lib.a. Jak więc nie wiedzieć, że plik obiektowy jest potrzebny podczas łączenia?

Ponieważ kompilacja i łączenie za pomocą jednego polecenia nie zmienia kolejności sekwencji łączenia.

Po uruchomieniu powyższego polecenia gccokazuje 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ą, którą zauważasz w przypadku niepowodzenia, jest to, że gcc wygenerował tymczasowy plik obiektowy w przypadku kompilacji + łącza, ponieważ nie mówisz, aby go używał eg1.o. Widzimy:

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

zamiast:

eg1.o: In function `main':

Zobacz też

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

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

Mike Kinghan
źródło
18

Szablony zaprzyjaźnienia ...

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 Tużywanego typu Foomusi istnieć szablon 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, linker nie może go znaleźć i powoduje błąd.

Aby to poprawić, możesz zadeklarować operator szablonu przed Footypem, a następnie zadeklarować jako przyjaciela 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żliwiając przyjaźni objęcie wszystkich instancji 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 operator<<może być wykonana inline wewnątrz definicji klasy;

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

Zauważ , że gdy deklaracja operatora (lub funkcji) pojawia się tylko w klasie, nazwa nie jest dostępna dla „normalnego” wyszukiwania, tylko dla wyszukiwania zależnego od argumentu, z cppreference ;

Nazwa po raz pierwszy zadeklarowana w deklaracji znajomego w klasie lub szablonie klasy X staje się członkiem najbardziej wewnętrznej otaczającej przestrzeni nazw X, ale nie jest dostępna dla wyszukiwania (z wyjątkiem wyszukiwania zależnego od argumentu, który uwzględnia X), chyba że pasująca deklaracja w zakresie przestrzeni nazw to opatrzony...

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

Lista kodów pokazująca powyższe techniki .


Jako notatka dodatkowa do nieudanego przykładu 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)

Niall
źródło
16

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

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

Jak działają linkery? Linker dopasowuje deklarację funkcji (zadeklarowaną w nagłówku) z jej definicją (w bibliotece współdzielonej) poprzez porównanie ich podpisów. Możesz otrzymać błąd linkera, jeśli linker nie znajdzie idealnie pasującej definicji funkcji.

Czy nadal można uzyskać błąd linkera, mimo że deklaracja i definicja wydają się pasować? 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 kończy się taka sytuacja? Uwzględnij oczywiście ścieżki ! Jeśli podczas kompilowania biblioteki współużytkowanej ścieżka włączania prowadzi do header1.hi używasz header2.hw swoim własnym programie, pozostaniesz drapiąc się po nagłówku, zastanawiając się, co się stało (zamierzona gra słów).

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

Dalsze opracowanie 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 dołącz do biblioteki w swoim własnym projekcie.

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

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

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

Zauważ, że w tym przykładzie linker powiedziałby ci, że nie mógł znaleźć draw(), kiedy w rzeczywistości wiesz, że jest on eksportowany przez bibliotekę. Mógłbyś godzinami drapać się w głowie, zastanawiając się, co poszło nie tak. Chodzi o to, że linker widzi inną sygnaturę, ponieważ typy parametrów są nieco inne. W tym przykładzie vec3jest 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łączone pochodzą z dwóch różnych wersji biblioteki).

Debugowanie linkera

DUMPBIN jest twoim przyjacielem, jeśli korzystasz z Visual Studio. Jestem pewien, że inne kompilatory mają inne podobne narzędzia.

Proces przebiega następująco:

  1. Zwróć uwagę na dziwnie zniekształconą nazwę podaną w błędzie linkera. (np. draw @ graphic @ XYZ).
  2. Zrzuć wyeksportowane symbole z biblioteki do pliku tekstowego.
  3. Wyszukaj wyeksportowany symbol zainteresowania i zauważ, że zniekształcona nazwa jest inna.
  4. Zwróć uwagę, dlaczego zniekształcone nazwy były inne. Zobaczysz, że typy parametrów są różne, nawet jeśli wyglądają tak samo w kodzie źródłowym.
  5. Powód, dlaczego się różnią. W powyższym przykładzie różnią się one z powodu różnych plików dołączeń.

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

EDYCJA 1: Przepisano pierwszą część, aby była łatwiejsza do zrozumienia. Proszę o komentarz poniżej, aby poinformować mnie, czy coś innego wymaga naprawy. Dzięki!

fafaro
źródło
15

Niespójne UNICODEdefinicje

Kompilacja Windows UNICODE jest budowana z TCHARitp. Zdefiniowanym jako wchar_titd. Gdy nie buduje się z UNICODEdefinicją zdefiniowaną jako kompilacja TCHARzdefiniowana jako charitd. Te UNICODEi _UNICODEdefinicje wpływają na wszystkie typy ciągów T ; LPTSTR, LPCTSTRI ich Ełku.

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

Błąd zwykle obejmuje funkcję o wartości typu pochodnego charlub wchar_t, mogą obejmować również std::basic_string<>itp. Podczas przeglądania funkcji dotkniętego w kodzie, to często będzie odniesienie do TCHARlub std::basic_string<TCHAR>itd. Jest to znak ostrzegawcze, że kod został pierwotnie przeznaczony zarówno dla Unicode i Multi-Byte Character (lub „taśmy”) build .

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

  1. Można to zrobić za pomocą albo;

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

    Właściwości projektu> Ogólne> Domyślne ustawienia projektu> Zestaw znaków

  3. Lub w wierszu poleceń;

    /DUNICODE /D_UNICODE

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

Nie zapomnij również zachować spójności między kompilacjami „Release” i „Debug”.

Niall
źródło
14

Oczyść i odbuduj

„Czysta” kompilacja może usunąć „martwe drewno”, które można pozostawić leżące wokół poprzednich kompilacji, kompilacji zakończonych niepowodzeniem, niekompletnych kompilacji i innych problemów kompilacji związanych z systemem kompilacji.

Zasadniczo IDE lub kompilacja będzie zawierać pewną formę funkcji „czystego”, ale może 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 „czystego” sprawdź, czy „czyste” powiodło się i czy wszystkie wygenerowane pliki pośrednie (np. Automatyczny plik makefile) zostały pomyślnie usunięte.

Proces ten można postrzegać jako ostateczność, ale często stanowi on dobry pierwszy krok ; zwłaszcza jeśli niedawno dodano kod związany z błędem (lokalnie lub ze źródłowego repozytorium).

Niall
źródło
10

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

Dla osób pochodzących z C może być zaskoczeniem, że w C ++ constzmienne globalne mają wewnętrzne (lub statyczne) powiązanie. W przypadku C tak nie było, ponieważ wszystkie zmienne globalne są domyślnie extern(tzn. 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łówka i dołączenie go do pliku2.cpp i pliku1.cpp

extern const int test;
extern int test2;

Alternatywnie można zadeklarować constzmienną w pliku1.cpp jawnieextern

Andreas H.
źródło
8

Mimo że jest to dość stare pytanie z wieloma zaakceptowanymi odpowiedziami, chciałbym podzielić się z Tobą, jak rozwiązać niejasny „niezdefiniowany odnośnik do” błędu.

Różne wersje bibliotek

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

#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

Załóżmy, że mam trzy pliki: main.cpp, file.h, file.cpp:

  • plik.h # include < eksperymentalny :: system plików > i zawiera powyższy kod
  • file.cpp , implementacja file.h, # include „ 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 < systemie plików >, używana wersja systemu plików to 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żda funkcja zawarta w pliku.o i używana w main.o, która wymagała,path_t dawała błędy „niezdefiniowanego odwołania”, ponieważ main.o odnosiło się do pliku.o,std::filesystem::path ale do . std::experimental::filesystem::path

Rozkład

Aby to naprawić, po prostu musiałem zmienić <eksperymentalny :: system plików> w pliku.h na <system plików> .

Stypox
źródło
5

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

Domyślne zachowanie gcc polega na tym, że wszystkie symbole są widoczne. Jednak gdy jednostki tłumaczeniowe są budowane z opcją -fvisibility=hidden, tylko funkcje / symbole oznaczone symbolem __attribute__ ((visibility ("default")))są zewnętrzne 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 

ukryte / lokalne symbole są oznaczone nmmał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ć nmtej opcji -Cdo rozplątania nazw (jeśli użyto C ++).

Podobnie jak w bibliotekach dll systemu Windows, funkcje publiczne można oznaczać za pomocą definicji, na przykład DLL_PUBLICzdefiniowanej jako:

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

DLL_PUBLIC int my_public_function(){
  ...
}

Co w przybliżeniu 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.


Kiedy jednostka tłumacząca jest kompilowana z -fvisibility=hiddenwynikowymi symbolami, nadal ma zewnętrzne powiązanie (pokazane za pomocą symbolu wielkimi literami nm) i może być używane do zewnętrznego łączenia bez problemu, jeśli pliki obiektowe staną się częścią bibliotek statycznych. Łączenie staje się lokalne tylko wtedy, gdy pliki obiektowe są połączone z biblioteką współdzieloną.

Aby sprawdzić, 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
ead
źródło
Powinieneś używać nm -CDlub nm -gCDdo przeglądania zewnętrznych symboli. Zobacz także Widoczność na wiki GCC.
jww
2

Różne architektury

Może zostać wyświetlony komunikat:

library machine type 'x64' conflicts with target machine type 'X86'

W takim przypadku oznacza to, że dostępne symbole dotyczą innej architektury niż ta, dla której się kompilujesz.

W Visual Studio dzieje się tak z powodu niewłaściwej „Platformy” i musisz albo wybrać właściwą, albo zainstalować odpowiednią wersję biblioteki.

W systemie Linux może to wynikać z niewłaściwego folderu biblioteki (przy użyciu liblib64 na przykład zamiast zamiast ).

W systemie MacOS istnieje możliwość wysyłki obu architektur do tego samego pliku. Może być tak, że link spodziewa się, że będą tam obie wersje, ale jest tylko jedna. Może to być również problem z niewłaściwym folderem lib/, w lib64którym biblioteka jest pobierana.

Matthieu Brucher
źródło