Was ist ein undefinierter Referenz- / ungelöster externer Symbolfehler und wie behebe ich ihn?

1493

Was sind undefinierte Referenz- / ungelöste externe Symbolfehler? Was sind häufige Ursachen und wie können sie behoben / verhindert werden?

Fühlen Sie sich frei, Ihre eigenen zu bearbeiten / hinzuzufügen.

Luchian Grigore
quelle
@LuchianGrigore ' zögern Sie nicht, eine Antwort hinzuzufügen' Ich habe es vorgezogen, den entsprechenden Link (IMHO) zu Ihrer primären Antwort hinzuzufügen, wenn Sie dies zulassen möchten.
πάντα ῥεῖ
19
Diese Frage ist viel zu allgemein, um eine nützliche Antwort zuzulassen. Ich kann nicht glauben, wie viele über 1000 Reputationsleute dies kommentieren, ohne die Frage abzuschießen. In der Zwischenzeit sehe ich viele Fragen, die für mich sinnvoll und sehr nützlich sind und die geschlossen sind.
Albert van der Horst
10
@AlbertvanderHorst "Diese Frage ist viel zu allgemein, um eine nützliche Antwort zuzulassen." Sind die folgenden Antworten nicht nützlich?
LF
4
Sie können nützlich sein, ebenso wie viele Antworten auf Fragen, die als zu allgemein gekennzeichnet sind.
Albert van der Horst
1
Ich würde gerne ein minimal reproduzierbares Beispiel als etwas sehen, das wir ehrlich gesagt von den meisten neuen Benutzern verlangen. Ich meine nichts damit, es ist nur so - wir können nicht erwarten, dass die Leute die Regeln befolgen, die wir uns selbst nicht auferlegen.
Danilo

Antworten:

850

Das Kompilieren eines C ++ - Programms erfolgt in mehreren Schritten, wie in 2.2 angegeben (Dank an Keith Thompson als Referenz) :

Die Priorität unter den Syntaxregeln für die Übersetzung wird in den folgenden Phasen festgelegt [siehe Fußnote] .

  1. Die Zeichen der physischen Quelldatei werden bei Bedarf implementierungsdefiniert dem grundlegenden Quellzeichensatz zugeordnet (Einführung neuer Zeilenzeichen für Zeilenende-Indikatoren). [SNIP]
  2. Jede Instanz eines Backslash-Zeichens (\), unmittelbar gefolgt von einem neuen Zeilenzeichen, wird gelöscht, wodurch physische Quellzeilen zu logischen Quellzeilen gespleißt werden. [SNIP]
  3. Die Quelldatei wird in Vorverarbeitungstoken (2.5) und Sequenzen von Leerzeichen (einschließlich Kommentaren) zerlegt. [SNIP]
  4. Vorverarbeitungsanweisungen werden ausgeführt, Makroaufrufe werden erweitert und _Pragma-unäre Operatorausdrücke werden ausgeführt. [SNIP]
  5. Jedes Quellzeichensatzelement in einem Zeichenliteral oder einem Zeichenfolgenliteral sowie jede Escape-Sequenz und jeder universelle Zeichenname in einem Zeichenliteral oder einem nicht rohen Zeichenfolgenliteral wird in das entsprechende Element des Ausführungszeichensatzes konvertiert. [SNIP]
  6. Benachbarte String-Literal-Token werden verkettet.
  7. Leerzeichen, die Token trennen, sind nicht mehr von Bedeutung. Jedes Vorverarbeitungstoken wird in ein Token konvertiert. (2.7). Die resultierenden Token werden syntaktisch und semantisch analysiert und als Übersetzungseinheit übersetzt.[SNIP]
  8. Übersetzte Übersetzungseinheiten und Instanziierungseinheiten werden wie folgt kombiniert: [SNIP]
  9. Alle externen Entitätsreferenzen werden aufgelöst. Bibliothekskomponenten werden verknüpft, um externe Verweise auf Entitäten zu erfüllen, die in der aktuellen Übersetzung nicht definiert sind. Alle diese Übersetzerausgaben werden in einem Programmabbild gesammelt, das Informationen enthält, die für die Ausführung in seiner Ausführungsumgebung benötigt werden. (Hervorhebung von mir)

[Fußnote] Implementierungen müssen sich so verhalten, als ob diese getrennten Phasen auftreten, obwohl in der Praxis verschiedene Phasen zusammengefaltet werden können.

Die angegebenen Fehler treten in dieser letzten Phase der Kompilierung auf, die am häufigsten als Verknüpfung bezeichnet wird. Dies bedeutet im Grunde, dass Sie eine Reihe von Implementierungsdateien zu Objektdateien oder Bibliotheken kompiliert haben und diese nun zusammenarbeiten möchten.

Angenommen, Sie haben das Symbol ain definiert a.cpp. Nun b.cpp erklärte dieses Symbol und benutzte es. Vor dem Verknüpfen wird einfach davon ausgegangen, dass dieses Symbol irgendwo definiert wurde , aber es ist noch egal, wo. Die Verknüpfungsphase ist dafür verantwortlich, das Symbol zu finden und korrekt zu verknüpfen b.cpp(also tatsächlich mit dem Objekt oder der Bibliothek, die es verwendet).

Wenn Sie Microsoft Visual Studio verwenden, werden Sie feststellen, dass Projekte .libDateien generieren . Diese enthalten eine Tabelle mit exportierten Symbolen und eine Tabelle mit importierten Symbolen. Die importierten Symbole werden für die Bibliotheken aufgelöst, mit denen Sie verknüpfen, und die exportierten Symbole werden für die Bibliotheken bereitgestellt, die diese verwenden.lib (falls vorhanden).

Ähnliche Mechanismen existieren für andere Compiler / Plattformen.

Häufige Fehlermeldungen error LNK2001, error LNK1120, error LNK2019für Microsoft Visual Studio und undefined reference to symbol für GCC .

Der Code:

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

generiert die folgenden Fehler mit 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

und ähnliche Fehler mit 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

Häufige Ursachen sind:

Luchian Grigore
quelle
16
Persönlich denke ich, dass die MS Linker-Fehlermeldungen genauso lesbar sind wie die GCC-Fehler. Sie haben auch den Vorteil, dass sowohl die verstümmelten als auch die nicht entwirrten Namen für das ungelöste Äußere enthalten sind. Der verstümmelte Name kann hilfreich sein, wenn Sie die Bibliotheken oder Objektdateien direkt anzeigen müssen, um festzustellen, wo das Problem liegen könnte (z. B. eine Nichtübereinstimmung der Aufrufkonvention). Ich bin mir auch nicht sicher, welche Version von MSVC die Fehler hier verursacht hat, aber neuere Versionen enthalten den Namen (sowohl entstellt als auch entwirrt) der Funktion, die sich auf das ungelöste externe Symbol bezieht.
Michael Burr
5
David Drysdale hat einen großartigen Artikel über die Funktionsweise von Linkern geschrieben: Anfängerleitfaden für Linker . Angesichts des Themas dieser Frage hielt ich es für nützlich.
Pressacco
@TankorSmash Verwenden Sie gcc? MinGW um genauer zu sein.
Doug65536
1
@ Luchian Es wäre schön, wenn Sie die richtigen hinzufügen und die oben genannten Fehler beheben
Phalgun
178

Klassenmitglieder:

Ein reines virtual Destruktor braucht eine Implementierung.

Um einen Destruktor als rein zu deklarieren, müssen Sie ihn noch definieren (im Gegensatz zu einer regulären Funktion):

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

Dies liegt daran, dass Basisklassendestruktoren aufgerufen werden, wenn das Objekt implizit zerstört wird, sodass eine Definition erforderlich ist.

virtual Methoden müssen entweder implementiert oder als rein definiert werden.

Dies ähnelt Nicht- virtualMethoden ohne Definition, mit der zusätzlichen Begründung, dass die reine Deklaration eine Dummy-vtable generiert und Sie möglicherweise den Linker-Fehler erhalten, ohne die Funktion zu verwenden:

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

Damit dies funktioniert, deklarieren Sie X::foo()als rein:

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

Nicht- virtualKlassenmitglieder

Einige Mitglieder müssen definiert werden, auch wenn sie nicht explizit verwendet werden:

struct A
{ 
    ~A();
};

Folgendes würde den Fehler ergeben:

A a;      //destructor undefined

Die Implementierung kann in der Klassendefinition selbst inline erfolgen:

struct A
{ 
    ~A() {}
};

oder draußen:

A::~A() {}

Befindet sich die Implementierung außerhalb der Klassendefinition, jedoch in einem Header, müssen die Methoden so markiert werden inline, dass eine Mehrfachdefinition verhindert wird.

Alle verwendeten Member-Methoden müssen definiert werden, wenn sie verwendet werden.

Ein häufiger Fehler ist das Vergessen, den Namen zu qualifizieren:

struct A
{
   void foo();
};

void foo() {}

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

Die Definition sollte sein

void A::foo() {}

staticDatenelemente müssen außerhalb der Klasse in einer einzigen Übersetzungseinheit definiert werden :

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

Ein Initialisierer kann für ein static constDatenelement vom Integral- oder Aufzählungstyp innerhalb der Klassendefinition bereitgestellt werden . Für die Verwendung dieses Mitglieds ist jedoch weiterhin eine Definition des Namespace-Bereichs erforderlich, wie oben beschrieben. C ++ 11 ermöglicht die Initialisierung innerhalb der Klasse für alle static constDatenelemente.

Luchian Grigore
quelle
Ich dachte nur, Sie möchten vielleicht betonen, dass beides möglich ist und der dtor eigentlich keine Ausnahme ist. (Es ist nicht aus Ihrer Formulierung auf den ersten Blick ersichtlich.)
Deduplicator
112

Fehler beim Verknüpfen mit geeigneten Bibliotheken / Objektdateien oder Kompilieren von Implementierungsdateien

Üblicherweise generiert jede Übersetzungseinheit eine Objektdatei, die die Definitionen der in dieser Übersetzungseinheit definierten Symbole enthält. Um diese Symbole zu verwenden, müssen Sie eine Verknüpfung zu diesen Objektdateien herstellen.

Unter gcc würden Sie alle Objektdateien angeben, die in der Befehlszeile miteinander verknüpft werden sollen, oder die Implementierungsdateien zusammen kompilieren.

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

Das libraryNamehier ist nur der bloße Name der Bibliothek ohne plattformspezifische Ergänzungen. So werden zB unter Linux normalerweise Bibliotheksdateien aufgerufen, libfoo.soaber Sie würden nur schreiben -lfoo. Unter Windows wird möglicherweise dieselbe Datei aufgerufen foo.lib, Sie verwenden jedoch dasselbe Argument. Möglicherweise müssen Sie das Verzeichnis hinzufügen, in dem diese Dateien gefunden werden können -L‹directory›. Stellen Sie sicher, dass nach -loder kein Leerzeichen steht-L .

Für XCode : Fügen Sie die Benutzerkopf-Suchpfade hinzu -> fügen Sie den Bibliothekssuchpfad hinzu -> ziehen Sie die tatsächliche Bibliotheksreferenz per Drag & Drop in den Projektordner.

Unter MSVS werden Dateien, die einem Projekt hinzugefügt wurden, automatisch mit ihren Objektdateien verknüpft, und es wird eine libDatei generiert (im allgemeinen Sprachgebrauch). Um die Symbole in einem separaten Projekt zu verwenden, müssen Sie die libDateien in die Projekteinstellungen aufnehmen. Dies erfolgt im Abschnitt Linker der Projekteigenschaften in Input -> Additional Dependencies. (Der Pfad zur libDatei sollte in hinzugefügt werden. Linker -> General -> Additional Library Directories) Wenn Sie eine Drittanbieter-Bibliothek verwenden, die mit einem versehen istlib Datei wird, führt dies normalerweise zu einem Fehler.

Es kann auch vorkommen, dass Sie vergessen, die Datei zur Kompilierung hinzuzufügen. In diesem Fall wird die Objektdatei nicht generiert. In gcc würden Sie die Dateien zur Befehlszeile hinzufügen. In MSVS die Datei Hinzufügen zum Projekt automatisch kompiliert (obwohl Dateien manuell einzeln vom Build ausgeschlossen werden können).

In der Windows-Programmierung ist das verräterische Zeichen dafür, dass Sie keine erforderliche Bibliothek verknüpft haben, dass der Name des nicht aufgelösten Symbols mit beginnt __imp_. Suchen Sie in der Dokumentation nach dem Namen der Funktion und geben Sie an, welche Bibliothek Sie verwenden müssen. Beispielsweise fügt MSDN die Informationen in ein Feld am Ende jeder Funktion in einem Abschnitt namens "Bibliothek" ein.

Luchian Grigore
quelle
1
Es wäre gut, wenn Sie explizit den häufigen Fehler von gcc main.cstatt behandeln könnten gcc main.c other.c (was Anfänger oft tun, bevor ihre Projekte so groß werden, dass sie .o-Dateien erstellen).
MM
101

Deklariert, aber keine Variable oder Funktion definiert.

Eine typische Variablendeklaration ist

extern int x;

Da dies nur eine Deklaration ist, ist eine einzige Definition erforderlich. Eine entsprechende Definition wäre:

int x;

Folgendes würde beispielsweise einen Fehler erzeugen:

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

Ähnliche Bemerkungen gelten für Funktionen. Das Deklarieren einer Funktion ohne sie zu definieren führt zu dem Fehler:

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

Achten Sie darauf, dass die von Ihnen implementierte Funktion genau mit der von Ihnen deklarierten übereinstimmt. Beispielsweise haben Sie möglicherweise nicht übereinstimmende Lebenslaufqualifizierer:

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)

Andere Beispiele für Fehlpaarungen sind

  • Funktion / Variable in einem Namespace deklariert, in einem anderen definiert.
  • Als Klassenmitglied deklarierte Funktion / Variable, definiert als global (oder umgekehrt).
  • Funktionsrückgabetyp, Parameternummer und -typen sowie Aufrufkonvention stimmen nicht alle genau überein.

Die Fehlermeldung des Compilers gibt Ihnen häufig die vollständige Deklaration der Variablen oder Funktion, die deklariert, aber nie definiert wurde. Vergleichen Sie es eng mit der von Ihnen angegebenen Definition. Stellen Sie sicher, dass jedes Detail übereinstimmt.

Luchian Grigore
quelle
In VS fallen CPP-Dateien, die mit denen im Header übereinstimmen, die #includesnicht zum Quellverzeichnis hinzugefügt wurden , ebenfalls unter die Kategorie der fehlenden Definitionen.
Laurie Stearn
86

Die Reihenfolge, in der voneinander abhängige verknüpfte Bibliotheken angegeben werden, ist falsch.

Die Reihenfolge, in der Bibliotheken verknüpft sind, spielt eine Rolle, wenn die Bibliotheken voneinander abhängen. Wenn die Bibliothek Avon der Bibliothek abhängt B, libA MUSS sie libBim Allgemeinen zuvor in den Linker-Flags angezeigt werden.

Zum Beispiel:

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

Erstellen Sie die Bibliotheken:

$ 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

Kompilieren:

$ 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

Um es noch einmal zu wiederholen, die Reihenfolge ist wichtig!

Svalorzen
quelle
Ich bin merkwürdig, dass ich in meinem Fall eine Objektdatei hatte, die von einer gemeinsam genutzten Bibliothek abhängt. Ich musste das Makefile ändern und die Bibliothek NACH dem Objekt mit gcc 4.8.4 auf Debian platzieren. Unter Centos 6.5 mit gcc 4.4 funktionierte das Makefile problemlos.
Marco Sulla
74

Was ist ein "undefinierter Verweis / ungelöstes externes Symbol"?

Ich werde versuchen zu erklären, was ein "undefinierter Verweis / ungelöstes externes Symbol" ist.

Hinweis: Ich benutze G ++ und Linux und alle Beispiele sind dafür

Zum Beispiel haben wir einen Code

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

und

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

Objektdateien erstellen

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

Nach der Assembler-Phase haben wir eine Objektdatei, die alle zu exportierenden Symbole enthält. Schau dir die Symbole an

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

Ich habe einige Zeilen von der Ausgabe abgelehnt, weil sie keine Rolle spielen

Wir sehen also folgende Symbole zum Exportieren.

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

src2.cpp exportiert nichts und wir haben keine seiner Symbole gesehen

Verknüpfen Sie unsere Objektdateien

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

und starte es

$ ./prog
123

Linker sieht exportierte Symbole und verknüpft sie. Jetzt versuchen wir, Zeilen in src2.cpp wie hier zu kommentieren

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

und erstellen Sie eine Objektdatei neu

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

OK (keine Fehler), da wir nur Objektdateien erstellen, ist die Verknüpfung noch nicht abgeschlossen. Versuchen Sie zu verlinken

$ 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

Dies ist passiert, weil unser local_var_name statisch ist, dh für andere Module nicht sichtbar ist. Jetzt tiefer. Holen Sie sich die Ausgabe der Übersetzungsphase

$ 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

Wir haben also gesehen, dass es keine Bezeichnung für local_var_name gibt. Deshalb hat Linker sie nicht gefunden. Aber wir sind Hacker :) und wir können es beheben. Öffnen Sie src1.s in Ihrem Texteditor und ändern Sie es

.local  _ZL14local_var_name
.comm   _ZL14local_var_name,4,4

zu

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

dh du solltest wie unten haben

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

Wir haben die Sichtbarkeit von local_var_name geändert und den Wert auf 456789 festgelegt. Versuchen Sie, daraus eine Objektdatei zu erstellen

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

ok, siehe Selbstausgabe (Symbole)

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

jetzt hat local_var_name Bind GLOBAL (war LOCAL)

Verknüpfung

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

und starte es

$ ./prog 
123456789

ok, wir hacken es :)

Infolgedessen tritt ein "undefinierter Referenz- / ungelöster externer Symbolfehler" auf, wenn der Linker keine globalen Symbole in den Objektdateien finden kann.

Kastaneda
quelle
71

Symbole wurden in einem C-Programm definiert und in C ++ - Code verwendet.

Die Funktion (oder Variable) void foo()wurde in einem C-Programm definiert und Sie versuchen, sie in einem C ++ - Programm zu verwenden:

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

Der C ++ - Linker erwartet, dass Namen entstellt werden. Daher müssen Sie die Funktion wie folgt deklarieren:

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

Entsprechend wurde die Funktion (oder Variable) nicht in einem C-Programm void foo()definiert, sondern in C ++, jedoch mit C-Verknüpfung:

extern "C" void foo();

und Sie versuchen, es in einem C ++ - Programm mit C ++ - Verknüpfung zu verwenden.

Wenn eine gesamte Bibliothek in einer Header-Datei enthalten ist (und als C-Code kompiliert wurde); Das Include muss wie folgt sein:

extern "C" {
    #include "cheader.h"
}
Luchian Grigore
quelle
5
Oder umgekehrt: Wenn Sie eine C-Bibliothek entwickeln, besteht eine nette Regel darin, die Header-Datei (en) zu schützen, indem Sie alle exportierten Deklarationen mit #ifdef __cplusplus [\n] extern"C" { [\n] #endifund umgeben #ifdef __cplusplus [\n] } [\n] #endif(dies [\n]ist ein echter Wagenrücklauf, aber ich kann dies nicht richtig in einen Kommentar schreiben).
Bentoy13
Wie im obigen Kommentar hat der Abschnitt 'Erstellen von Headern in
zanbri
Hat mir wirklich geholfen! Dies war mein Fall, als ich nach dieser Frage suchte
Knightofhorizon
Dies kann auch passieren, wenn Sie versehentlich Ihre normale C ++ - Headerdatei einschließen, die von externem C : umgeben ist extern "C" { #include <myCppHeader.h> }.
ComFreek
68

Wenn alles andere fehlschlägt, kompilieren Sie neu.

Ich konnte kürzlich einen ungelösten externen Fehler in Visual Studio 2012 beseitigen, indem ich die fehlerhafte Datei neu kompilierte. Als ich wieder aufgebaut habe, ist der Fehler verschwunden.

Dies geschieht normalerweise, wenn zwei (oder mehr) Bibliotheken eine zyklische Abhängigkeit aufweisen. Bibliothek A versucht, Symbole in B.lib zu verwenden, und Bibliothek B versucht, Symbole aus A.lib zu verwenden. Beides existiert nicht, um damit zu beginnen. Wenn Sie versuchen, A zu kompilieren, schlägt der Verknüpfungsschritt fehl, da B.lib nicht gefunden werden kann. A.lib wird generiert, aber keine DLL. Anschließend kompilieren Sie B, was erfolgreich sein wird, und generieren B.lib. Das erneute Kompilieren von A funktioniert jetzt, da B.lib jetzt gefunden wird.

sgryzko
quelle
1
Richtig - Dies geschieht, wenn Bibliotheken eine zyklische Abhängigkeit haben.
Luchian Grigore
1
Ich habe Ihre Antwort erweitert und in der Hauptantwort verlinkt. Vielen Dank.
Luchian Grigore
57

Fehler beim Importieren / Exportieren von Methoden / Klassen über Module / DLL (compilerspezifisch).

MSVS erfordert , dass Sie , welche Symbole auf den Export und Import angeben Verwendung __declspec(dllexport)und__declspec(dllimport) .

Diese doppelte Funktionalität wird normalerweise durch die Verwendung eines Makros erreicht:

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

Das Makro wird THIS_MODULEnur in dem Modul definiert, das die Funktion exportiert. Auf diese Weise lautet die Erklärung:

DLLIMPEXP void foo();

erweitert sich auf

__declspec(dllexport) void foo();

und weist den Compiler an, die Funktion zu exportieren, da das aktuelle Modul seine Definition enthält. Wenn die Deklaration in ein anderes Modul aufgenommen wird, wird sie auf erweitert

__declspec(dllimport) void foo();

und teilt dem Compiler mit, dass sich die Definition in einer der Bibliotheken befindet, mit denen Sie verknüpft haben (siehe auch 1). ).

Sie können ähnliche Import- / Exportklassen verwenden:

class DLLIMPEXP X
{
};
Luchian Grigore
quelle
2
Um vollständig zu sein, sollten in dieser Antwort visibilitydie .defDateien von GCC und Windows erwähnt werden, da diese auch den Symbolnamen und die Präsenz beeinflussen.
Rubenvb
@rubenvb Ich habe seit Ewigkeiten keine .defDateien mehr verwendet . Fühlen Sie sich frei, eine Antwort hinzuzufügen oder diese zu bearbeiten.
Luchian Grigore
57

Dies ist eine der verwirrendsten Fehlermeldungen, die jeder VC ++ - Programmierer immer wieder gesehen hat. Lassen Sie uns zuerst die Dinge klarer machen.

A. Was ist ein Symbol? Kurz gesagt, ein Symbol ist ein Name. Dies kann ein Variablenname, ein Funktionsname, ein Klassenname, ein Typedef-Name oder etwas anderes sein als die Namen und Zeichen, die zur C ++ - Sprache gehören. Es wird benutzerdefiniert oder von einer Abhängigkeitsbibliothek (einer anderen benutzerdefinierten Bibliothek) eingeführt.

B. Was ist extern? In VC ++ wird jede Quelldatei (.cpp, .c usw.) Als Übersetzungseinheit betrachtet, der Compiler kompiliert jeweils eine Einheit und generiert eine Objektdatei (.obj) für die aktuelle Übersetzungseinheit. (Beachten Sie, dass jede Header-Datei, die in dieser Quelldatei enthalten ist, vorverarbeitet wird und als Teil dieser Übersetzungseinheit betrachtet wird.) Alles innerhalb einer Übersetzungseinheit wird als intern betrachtet, alles andere wird als extern betrachtet. In C ++ können Sie auf ein externes Symbol verweisen, indem Sie Schlüsselwörter wie extern:__declspec (dllimport) und so weiter.

C. Was ist "Entschlossenheit"? Auflösen ist ein Verknüpfungszeitbegriff. In der Verknüpfungszeit versucht der Linker, die externe Definition für jedes Symbol in Objektdateien zu finden, die seine Definition nicht intern finden können. Der Umfang dieses Suchvorgangs umfasst:

  • Alle Objektdateien, die in der Kompilierungszeit generiert wurden
  • Alle Bibliotheken (.lib), die entweder explizit oder implizit als zusätzliche Abhängigkeiten dieser Gebäudeanwendung angegeben werden.

Dieser Suchvorgang wird als Auflösung bezeichnet.

D. Warum schließlich ungelöstes externes Symbol? Wenn der Linker die externe Definition für ein Symbol ohne interne Definition nicht finden kann, meldet er einen Fehler mit einem nicht aufgelösten externen Symbol.

E. Mögliche Ursachen für LNK2019 : Ungelöster externer Symbolfehler . Wir wissen bereits, dass dieser Fehler darauf zurückzuführen ist, dass der Linker die Definition externer Symbole nicht gefunden hat. Die möglichen Ursachen können wie folgt sortiert werden:

  1. Definition existiert

Wenn wir zum Beispiel eine Funktion namens foo haben, die in a.cpp definiert ist:

int foo()
{
    return 0;
}

In b.cpp wollen wir die Funktion foo aufrufen, also fügen wir hinzu

void foo();

Um die Funktion foo () zu deklarieren und in einem anderen Funktionskörper aufzurufen, sagen Sie bar():

void bar()
{
    foo();
}

Wenn Sie diesen Code erstellen, wird ein LNK2019-Fehler angezeigt, der sich darüber beschwert, dass foo ein ungelöstes Symbol ist. In diesem Fall wissen wir, dass foo () seine Definition in a.cpp hat, sich jedoch von der von uns aufgerufenen unterscheidet (anderer Rückgabewert). Dies ist der Fall, wenn eine Definition existiert.

  1. Definition existiert nicht

Wenn wir einige Funktionen in einer Bibliothek aufrufen möchten, die Importbibliothek jedoch nicht zur zusätzlichen Abhängigkeitsliste (festgelegt von :) Project | Properties | Configuration Properties | Linker | Input | Additional DependencyIhrer Projekteinstellung hinzugefügt wird . Jetzt meldet der Linker einen LNK2019, da die Definition im aktuellen Suchbereich nicht vorhanden ist.

Nima Soroush
quelle
1
Vielen Dank für die systematische Erklärung. Ich konnte die anderen Antworten hier verstehen, nachdem ich diese gelesen hatte.
displayName
55

Vorlagenimplementierungen nicht sichtbar.

Bei nicht spezialisierten Vorlagen müssen die Definitionen für alle Übersetzungseinheiten sichtbar sein, die sie verwenden. Das heißt, Sie können die Definition einer Vorlage nicht von einer Implementierungsdatei trennen. Wenn Sie die Implementierung trennen müssen, besteht die übliche Problemumgehung darin, eine implDatei am Ende des Headers anzugeben, der die Vorlage deklariert. Eine häufige Situation ist:

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

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

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

Um dies zu beheben, müssen Sie die Definition von verschieben X::foo in die Header-Datei oder an eine Stelle verschieben, die für die Übersetzungseinheit, die sie verwendet, sichtbar ist.

Spezialisierte Vorlagen können in einer Implementierungsdatei implementiert werden und die Implementierung muss nicht sichtbar sein, aber die Spezialisierung muss zuvor deklariert werden.

Weitere Erklärungen und eine andere mögliche Lösung (explizite Instanziierung) finden Sie in dieser Frage und Antwort .

Luchian Grigore
quelle
1
Weitere Informationen zu "Vorlagen müssen im Header definiert sein" finden Sie unter stackoverflow.com/questions/495021
PlasmaHH
41

undefinierter Verweis auf WinMain@16oder ähnliche 'ungewöhnliche' main() Einstiegspunktreferenz (insbesondere für).

Möglicherweise haben Sie es versäumt, den richtigen Projekttyp mit Ihrer tatsächlichen IDE auszuwählen. Die IDE möchte möglicherweise z. B. Windows-Anwendungsprojekte anstelle der häufig verwendeten int main(int argc, char** argv);Signatur an eine solche Einstiegspunktfunktion (wie in der fehlenden Referenz oben angegeben) binden .

Wenn Ihre IDE Plain Console-Projekte unterstützt, möchten Sie möglicherweise diesen Projekttyp anstelle eines Windows-Anwendungsprojekts auswählen.


Hier werden case1 und case2 anhand eines realen Problems ausführlicher behandelt .

πάντα ῥεῖ
quelle
2
Ich kann nicht anders, als auf diese Frage hinzuweisen und die Tatsache , dass dies häufiger dadurch verursacht wird, dass man überhaupt keine Hauptfunktion hat als keine WinMain. Gültige C ++ - Programme benötigen a main.
Chris
36

Wenn Sie Bibliotheken von Drittanbietern verwenden, stellen Sie sicher, dass Sie über die richtigen 32/64-Bit-Binärdateien verfügen

Dula
quelle
34

Microsoft bietet eine Möglichkeit, #pragmazum Zeitpunkt der Verknüpfung auf die richtige Bibliothek zu verweisen.

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

Zusätzlich zum Bibliothekspfad einschließlich des Verzeichnisses der Bibliothek sollte dies der vollständige Name der Bibliothek sein.

Niall
quelle
34

Das Visual Studio NuGet-Paket muss für die neue Toolset-Version aktualisiert werden

Ich hatte gerade dieses Problem beim Versuch, libpng mit Visual Studio 2013 zu verknüpfen. Das Problem ist, dass die Paketdatei nur Bibliotheken für Visual Studio 2010 und 2012 enthielt.

Die richtige Lösung besteht darin, zu hoffen, dass der Entwickler ein aktualisiertes Paket veröffentlicht und dann aktualisiert. Für mich hat es jedoch funktioniert, indem eine zusätzliche Einstellung für VS2013 gehackt wurde, die auf die VS2012-Bibliotheksdateien verweist.

Ich habe das Paket (im packagesOrdner im Verzeichnis der Lösung) bearbeitet, indem ich packagename\build\native\packagename.targetsin dieser Datei alle v110Abschnitte gefunden und kopiert habe . Ich änderte die v110auf v120in den Bedingungsfelder nur sehr vorsichtig die Dateipfade alle zu verlassen v110. Dadurch konnte Visual Studio 2013 einfach eine Verknüpfung zu den Bibliotheken für 2012 herstellen, und in diesem Fall funktionierte es.

Malvineous
quelle
1
Dies scheint zu spezifisch - vielleicht wäre ein neuer Thread ein besserer Ort für diese Antwort.
Luchian Grigore
3
@LuchianGrigore: Ich wollte hier posten , da diese Frage genau dieses Problem war, aber sie wurde als Duplikat dieser Frage markiert, sodass ich sie dort nicht beantworten konnte. Also habe ich stattdessen meine Antwort hier gepostet.
Malvineous
Diese Frage hat bereits eine akzeptierte Antwort. Es ist als Duplikat markiert, da die allgemeine Ursache oben aufgeführt ist. Was würde passieren, wenn wir hier für jedes Problem mit einer Bibliothek, die nicht enthalten ist, eine Antwort hätten?
Luchian Grigore
6
@LuchianGrigore: Dieses Problem ist nicht bibliotheksspezifisch, sondern betrifft alle Bibliotheken, die das Paketverwaltungssystem von Visual Studio verwenden. Ich habe gerade die andere Frage gefunden, weil wir beide Probleme mit libpng hatten. Ich hatte auch das gleiche Problem (mit der gleichen Lösung) für libxml2, libiconv und glew. Diese Frage bezieht sich auf ein Problem mit dem Paketverwaltungssystem von Visual Studio. Meine Antwort erläutert den Grund und bietet eine Problemumgehung. Jemand hat gerade "ungelöstes externes" gesehen und angenommen, dass es sich um ein Standard-Linker-Problem handelt, wenn es sich tatsächlich um ein Paketverwaltungsproblem handelt.
Malvineous
34

Angenommen, Sie haben ein großes Projekt in C ++ geschrieben, das tausend CPP-Dateien und tausend H-Dateien enthält. Nehmen wir an, das Projekt hängt auch von zehn statischen Bibliotheken ab. Nehmen wir an, wir arbeiten unter Windows und erstellen unser Projekt in Visual Studio 20xx. Wenn Sie Strg + F7 Visual Studio drücken, um mit dem Kompilieren der gesamten Lösung zu beginnen (nehmen wir an, wir haben nur ein Projekt in der Lösung).

Was bedeutet Zusammenstellung?

  • Visual Studio sucht in der Datei .vcxproj und beginnt mit dem Kompilieren jeder Datei mit der Erweiterung .cpp. Die Reihenfolge der Kompilierung ist undefiniert. Sie dürfen also nicht davon ausgehen, dass die Datei main.cpp zuerst kompiliert wird
  • Wenn .cpp-Dateien von zusätzlichen .h-Dateien abhängen, um Symbole zu finden, die möglicherweise in der .cpp-Datei definiert sind oder nicht
  • Wenn eine CPP-Datei vorhanden ist, in der der Compiler kein Symbol finden konnte, wird die Meldung durch einen Compiler-Zeitfehler ausgelöst Symbol x nicht gefunden wurde
  • Für jede Datei mit der Erweiterung .cpp wird eine Objektdatei .o generiert, und Visual Studio schreibt die Ausgabe in eine Datei mit dem Namen ProjectName.Cpp.Clean.txt, die alle Objektdateien enthält, die vom Linker verarbeitet werden müssen.

Der zweite Schritt der Kompilierung wird von Linker ausgeführt. Linker sollte die gesamte Objektdatei zusammenführen und schließlich die Ausgabe erstellen (die eine ausführbare Datei oder eine Bibliothek sein kann).

Schritte zum Verknüpfen eines Projekts

  • Analysieren Sie alle Objektdateien und suchen Sie die Definition, die nur in Headern deklariert wurde (z. B.: Der Code einer Methode einer Klasse, wie in den vorherigen Antworten erwähnt, oder die Initialisierung einer statischen Variablen, die Mitglied einer Klasse ist).
  • Wenn ein Symbol in Objektdateien nicht gefunden werden konnte, wird er auch in Zusätzliche Bibliotheken gesucht. Zum Hinzufügen einer neuen Bibliothek zu einem Projekt Konfigurationseigenschaften -> VC ++ - Verzeichnisse -> Bibliotheksverzeichnisse. Hier haben Sie einen zusätzlichen Ordner für die Suche nach Bibliotheken und Konfigurationseigenschaften angegeben -> Linker -> Eingabe zur Angabe des Namens der Bibliothek. -Wenn der Linker das Symbol, das Sie in eine .cpp schreiben, nicht finden konnte, löst er einen Linker-Zeitfehler aus, der möglicherweise klingt error LNK2001: unresolved external symbol "void __cdecl foo(void)" (?foo@@YAXXZ)

Überwachung

  1. Sobald der Linker ein Symbol gefunden hat, sucht er nicht mehr in anderen Bibliotheken danach
  2. Die Reihenfolge der Verknüpfung von Bibliotheken spielt eine Rolle .
  3. Wenn Linker ein externes Symbol in einer statischen Bibliothek findet, nimmt er das Symbol in die Ausgabe des Projekts auf. Wenn die Bibliothek jedoch gemeinsam genutzt wird (dynamisch), nimmt er den Code (die Symbole) nicht in die Ausgabe auf, es kann jedoch zu Abstürzen zur Laufzeit kommen auftreten

So lösen Sie diese Art von Fehler

Compiler-Zeitfehler:

  • Stellen Sie sicher, dass Sie Ihr C ++ - Projekt syntaktisch korrekt schreiben.

Linker-Zeitfehler

  • Definieren Sie alle Symbole, die Sie in Ihren Header-Dateien deklarieren
  • Verwenden #pragma onceSie diese Option, um dem Compiler zu erlauben, keinen Header einzuschließen, wenn dieser bereits in der aktuellen kompilierten CPP enthalten war
  • Stellen Sie sicher, dass Ihre externe Bibliothek keine Symbole enthält, die mit anderen Symbolen, die Sie in Ihren Header-Dateien definiert haben, in Konflikt geraten können
  • Wenn Sie die Vorlage verwenden, um sicherzustellen, dass Sie die Definition jeder Vorlagenfunktion in die Header-Datei aufnehmen, damit der Compiler den entsprechenden Code für alle Instanziierungen generieren kann.
eFarzad
quelle
Ist Ihre Antwort nicht spezifisch für Visual Studio? In der Frage werden keine IDE- / Compiler-Tools angegeben, sodass Ihre Antwort für nicht visuelle Studio-Teile unbrauchbar wird.
Victor Polevoy
Du hast recht . Aber jeder in jedem IDE-Prozess des Kompilierens / Verlinkens wird etwas anders ausgeführt. Aber die Dateien werden genau gleich verarbeitet (sogar g ++ macht dasselbe, wenn es die Flags analysiert ..)
Das Problem betrifft nicht die IDE, sondern eine Antwort auf Verknüpfungsprobleme. Verknüpfungsprobleme hängen nicht mit der IDE zusammen, sondern mit dem Compiler und dem Erstellungsprozess.
Victor Polevoy
Ja. Aber der Erstellungs- / Verknüpfungsprozess wird in g ++ / Visual Studio (von Microsoft für VS bereitgestellter Compiler) / Eclipse / Net Beans auf die gleiche Weise ausgeführt
29

Ein Fehler im Compiler / in der IDE

Ich hatte kürzlich dieses Problem und es stellte sich heraus, dass es ein Fehler in Visual Studio Express 2013 war . Ich musste eine Quelldatei aus dem Projekt entfernen und erneut hinzufügen, um den Fehler zu beheben.

Schritte, um zu versuchen, wenn Sie glauben, dass es ein Fehler in Compiler / IDE sein könnte:

  • Bereinigen Sie das Projekt (einige IDEs haben die Möglichkeit, dies zu tun. Sie können dies auch manuell tun, indem Sie die Objektdateien löschen.)
  • Versuchen Sie, ein neues Projekt zu starten, und kopieren Sie den gesamten Quellcode vom ursprünglichen.
entwicklermw
quelle
5
Der Glaube, dass Ihre Werkzeuge kaputt sind, wird Sie höchstwahrscheinlich von der wahren Ursache ablenken. Es ist nur so viel wahrscheinlicher, dass Sie einen Fehler gemacht haben, als ein Compiler Ihr Problem verursacht hat. Das Bereinigen Ihrer Lösung oder das erneute Erstellen Ihrer Build-Konfiguration kann Build-Fehler beheben. Dies bedeutet jedoch nicht, dass der Compiler einen Fehler enthält. Das verknüpfte "Es stellte sich heraus, dass es ein Fehler war" wird von Microsoft nicht bestätigt und ist nicht reproduzierbar.
JDiMatteo
4
@JDiMatteo Es gibt 21 Antworten auf diese Frage und daher wird eine erhebliche Anzahl von Antworten keine "wahrscheinliche" Lösung sein. Wenn Sie alle Antworten ablehnen, die unter Ihrer Wahrscheinlichkeitsschwelle liegen, wird diese Seite praktisch unbrauchbar, da die meisten häufigen Fälle ohnehin leicht zu erkennen sind.
Entwickler
27

Verwenden Sie den Linker, um den Fehler zu diagnostizieren

Die meisten modernen Linker enthalten eine ausführliche Option, die in unterschiedlichem Maße gedruckt wird.

  • Linkaufruf (Befehlszeile),
  • Daten darüber, welche Bibliotheken in der Linkphase enthalten sind,
  • Der Standort der Bibliotheken,
  • Verwendete Suchpfade.

Für gcc und clang; Sie würden normalerweise -v -Wl,--verboseoder -v -Wl,-vzur Befehlszeile hinzufügen . Weitere Details finden Sie hier;

Bei MSVC wird /VERBOSE(insbesondere /VERBOSE:LIB) der Link-Befehlszeile hinzugefügt.

Niall
quelle
26

Die verknüpfte LIB-Datei ist einer DLL zugeordnet

Ich hatte das gleiche Problem. Angenommen, ich habe Projekte MyProject und TestProject. Ich hatte die lib-Datei für MyProject effektiv mit dem TestProject verknüpft. Diese lib-Datei wurde jedoch erstellt, als die DLL für das MyProject erstellt wurde. Außerdem enthielt ich nicht für alle Methoden im MyProject Quellcode, sondern nur Zugriff auf die Einstiegspunkte der DLL.

Um das Problem zu lösen, habe ich das MyProject als LIB erstellt und TestProject mit dieser LIB-Datei verknüpft (ich kopiere die generierte LIB-Datei und füge sie in den TestProject-Ordner ein). Ich kann dann MyProject erneut als DLL erstellen. Es wird kompiliert, da die Bibliothek, mit der TestProject verknüpft ist, Code für alle Methoden in Klassen in MyProject enthält.

kiriloff
quelle
25

Da die Leute bei Linkerfehlern auf diese Frage gerichtet zu sein scheinen, werde ich dies hier hinzufügen.

Ein möglicher Grund für Linkerfehler mit GCC 5.2.0 ist, dass jetzt standardmäßig eine neue libstdc ++ - Bibliothek ABI ausgewählt ist.

Wenn Sie Linkerfehler über undefinierte Verweise auf Symbole erhalten, die Typen im Namespace std :: __ cxx11 oder im Tag [abi: cxx11] enthalten, weist dies wahrscheinlich darauf hin, dass Sie versuchen, Objektdateien zu verknüpfen, die mit unterschiedlichen Werten für _GLIBCXX_USE_CXX11_ABI kompiliert wurden Makro. Dies ist häufig der Fall, wenn eine Verknüpfung zu einer Drittanbieter-Bibliothek hergestellt wird, die mit einer älteren Version von GCC kompiliert wurde. Wenn die Bibliothek eines Drittanbieters mit dem neuen ABI nicht wiederhergestellt werden kann, müssen Sie Ihren Code mit dem alten ABI neu kompilieren.

Wenn Sie also beim Wechsel zu einem GCC nach 5.1.0 plötzlich Linkerfehler erhalten, sollten Sie dies überprüfen.

Plankalkül
quelle
20

Ein Wrapper um GNU ld, der keine Linker-Skripte unterstützt

Einige .so-Dateien sind tatsächlich GNU ld-Linker-Skripte , z. B. ist die Datei libtbb.so eine ASCII-Textdatei mit folgendem Inhalt:

INPUT (libtbb.so.2)

Einige komplexere Builds unterstützen dies möglicherweise nicht. Wenn Sie beispielsweise -v in die Compileroptionen aufnehmen, können Sie sehen, dass der Mainwin-Gcc-Wrapper mwdip Linker-Skriptbefehlsdateien in der ausführlichen Ausgabeliste der zu verknüpfenden Bibliotheken verwirft. Eine einfache Lösung besteht darin, den Linker-Skript-Eingabebefehl zu ersetzen Datei mit einer Kopie der Datei (oder einem Symlink), z

cp libtbb.so.2 libtbb.so

Oder Sie können das Argument -l durch den vollständigen Pfad des .so ersetzen, z. B. anstelle von -ltbbdo/home/foo/tbb-4.3/linux/lib/intel64/gcc4.4/libtbb.so.2

JDiMatteo
quelle
20

Ihre Verknüpfung belegt Bibliotheken vor den Objektdateien, die auf sie verweisen

  • Sie versuchen, Ihr Programm zu kompilieren und mit der GCC-Toolchain zu verknüpfen.
  • Ihre Verknüpfung gibt alle erforderlichen Bibliotheken und Bibliothekssuchpfade an
  • Wenn es darauf libfooankommt libbar, dann setzt Ihre Verknüpfung richtig libfoovor libbar.
  • Ihre Verknüpfung nicht mit undefined reference to etwas Fehlern.
  • Aber alle undefiniert etwas s sind in den Header - Dateien deklariert haben Sie #included und sind in der Tat in den Bibliotheken definiert , die Sie verknüpfen.

Beispiele sind in C. Sie könnten genauso gut C ++ sein

Ein minimales Beispiel für eine statische Bibliothek, die Sie selbst erstellt haben

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

Sie erstellen Ihre statische Bibliothek:

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

Sie kompilieren Ihr Programm:

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

Sie versuchen es zu verknüpfen libmy_lib.aund scheitern:

$ 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

Das gleiche Ergebnis, wenn Sie in einem Schritt kompilieren und verknüpfen, wie:

$ 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

Ein minimales Beispiel für eine gemeinsam genutzte Systembibliothek ist die Komprimierungsbibliothek libz

eg2.c

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

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

Kompilieren Sie Ihr Programm:

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

Versuchen Sie, Ihr Programm mit zu verknüpfen, libzund schlagen Sie fehl:

$ 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

Gleiches gilt, wenn Sie auf einmal kompilieren und verknüpfen:

$ 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

Und eine Variation von Beispiel 2 mit pkg-config:

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

Was machst du falsch

In der Reihenfolge der Objektdateien und Bibliotheken, die Sie verknüpfen möchten, um Ihr Programm zu erstellen, platzieren Sie die Bibliotheken vor den Objektdateien, die auf sie verweisen. Sie müssen die Bibliotheken nach platzieren den Objektdateien platzieren, die auf sie verweisen.

Beispiel 1 richtig verknüpfen:

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

Erfolg:

$ ./eg1 
Hello World

Beispiel 2 richtig verknüpfen:

$ gcc -o eg2 eg2.o -lz

Erfolg:

$ ./eg2 
1.2.8

Verknüpfen Sie die pkg-configVariante von Beispiel 2 richtig:

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

Die Erklärung

Das Lesen ist ab hier optional .

Standardmäßig verbraucht ein von GCC in Ihrer Distribution generierter Verknüpfungsbefehl die Dateien in der Verknüpfung von links nach rechts in der Befehlszeilenreihenfolge. Wenn festgestellt wird, dass eine Datei auf etwas verweist und keine Definition dafür enthält, wird in Dateien weiter rechts nach einer Definition gesucht. Wenn es schließlich eine Definition findet, wird die Referenz aufgelöst. Wenn Referenzen am Ende ungelöst bleiben, schlägt die Verknüpfung fehl: Der Linker sucht nicht rückwärts.

Zunächst Beispiel 1 mit statischer Bibliothekmy_lib.a

Eine statische Bibliothek ist ein indiziertes Archiv von Objektdateien. Wenn der Linker -lmy_libin der Verknüpfungssequenz feststellt , dass sich dies auf die statische Bibliothek bezieht ./libmy_lib.a, möchte er wissen, ob Ihr Programm eine der Objektdateien in benötigt libmy_lib.a.

Es gibt nur Objektdatei in libmy_lib.a, nämlich my_lib.o, und es gibt nur eine Sache der Definition in my_lib.o, nämlich die Funktion hw.

Der Linker entscheidet, dass Ihr Programm my_lib.ogenau dann benötigt, wenn er bereits weiß, dass Ihr Programm auf hweine oder mehrere der Objektdateien verweist , die es bereits zum Programm hinzugefügt hat, und dass keine der Objektdateien, die es bereits hinzugefügt hat, eine enthält Definition für hw.

Wenn dies zutrifft, extrahiert der Linker eine Kopie my_lib.oaus der Bibliothek und fügt sie Ihrem Programm hinzu. Dann enthält das Programm eine Definition für hw, so seine Verweise auf hwsind aufgelöst .

Wenn Sie versuchen, das Programm wie folgt zu verknüpfen:

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

Der Linker hat eg1.o dem Programm nichts hinzugefügt , wenn er es sieht -lmy_lib. Denn zu diesem Zeitpunkt hat es nicht gesehen eg1.o. Ihr Programm macht noch keine Verweise auf hw: es hat noch keine Referenzen machen überhaupt , denn alle Referenzen macht es in sind eg1.o.

Der Linker fügt sich also nicht my_lib.oin das Programm ein und hat keine weitere Verwendung für libmy_lib.a.

Als nächstes wird es gefunden eg1.ound als Programm hinzugefügt. Eine Objektdatei in der Verknüpfungssequenz wird immer zum Programm hinzugefügt. Das Programm verweist nun auf hwund enthält keine Definition von hw; In der Verknüpfungssequenz ist jedoch nichts mehr vorhanden, was die fehlende Definition liefern könnte. Der Verweis auf hwwird ungelöst und die Verknüpfung schlägt fehl.

Zweitens Beispiel 2 mit gemeinsam genutzter Bibliotheklibz

Eine gemeinsam genutzte Bibliothek ist kein Archiv von Objektdateien oder Ähnlichem. Es ist viel mehr wie ein Programm , das keine mainFunktion hat und stattdessen mehrere andere Symbole verfügbar macht, die es definiert, damit andere Programme sie zur Laufzeit verwenden können.

Viele Linux - Distributionen heute konfigurieren ihre GCC Toolchain , so dass seine Sprache Treiber ( gcc, g++, gfortranusw.) anweisen , das System - Linker ( ld) zu Link Shared Libraries auf einer as-needed Basis. Sie haben eine dieser Distributionen.

Dies bedeutet, dass der Linker, wenn er -lzin der Verknüpfungssequenz findet und herausfindet, dass sich dies auf die gemeinsam genutzte Bibliothek bezieht (z. B.) /usr/lib/x86_64-linux-gnu/libz.so, wissen möchte, ob Referenzen, die er zu Ihrem Programm hinzugefügt hat und die noch nicht definiert sind, Definitionen haben exportiert vonlibz

Wenn das wahr ist, dann wird der Linker nicht kopieren Sie alle Stücke aus libzund fügen Sie sie in Ihrem Programm; Stattdessen wird nur der Code Ihres Programms behandelt, so dass: -

  • Zur Laufzeit libzlädt der Systemprogrammlader eine Kopie von in denselben Prozess wie Ihr Programm, wenn er eine Kopie Ihres Programms lädt, um es auszuführen.

  • Wenn Ihr Programm zur Laufzeit auf etwas verweist, das in definiert ist libz, verwendet diese Referenz die Definition, die von der Kopie von libzim selben Prozess exportiert wurde .

Ihr Programm möchte sich nur auf eine Sache beziehen, von der eine Definition exportiert wurde libz, nämlich die Funktion zlibVersion, auf die nur einmal in verwiesen wird eg2.c. Wenn der Linker diesen Verweis zu Ihrem Programm hinzufügt und dann die von exportierte Definition findet libz, wird der Verweis aufgelöst

Aber wenn Sie versuchen, das Programm wie folgt zu verlinken:

gcc -o eg2 -lz eg2.o

Die Reihenfolge der Ereignisse ist genauso falsch wie in Beispiel 1. Zu dem Zeitpunkt, an dem der Linker feststellt -lz, gibt es keine Verweise auf irgendetwas im Programm: Sie sind alle in eg2.o, was noch nicht gesehen wurde. Der Linker entscheidet also, dass er keine Verwendung hat libz. Wenn es erreicht ist eg2.o, es dem Programm hinzufügt und dann einen undefinierten Verweis auf hat zlibVersion, ist die Verknüpfungssequenz beendet; Diese Referenz ist nicht aufgelöst und die Verknüpfung schlägt fehl.

Schließlich hat die pkg-configVariation von Beispiel 2 eine jetzt offensichtliche Erklärung. Nach der Schalenerweiterung:

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

wird:

gcc -o eg2 -lz eg2.o

Das ist wieder nur Beispiel 2.

Ich kann das Problem in Beispiel 1 reproduzieren, aber nicht in Beispiel 2

Die Verknüpfung:

gcc -o eg2 -lz eg2.o

funktioniert gut für dich!

(Oder: Diese Verknüpfung hat beispielsweise bei Fedora 23 gut funktioniert, schlägt jedoch unter Ubuntu 16.04 fehl.)

Dies liegt daran, dass die Distribution, in der die Verknüpfung funktioniert, eine der Distributionen ist, die ihre GCC-Toolchain nicht so konfiguriert, dass gemeinsam genutzte Bibliotheken nach Bedarf verknüpft werden .

Früher war es für Unix-ähnliche Systeme normal, statische und gemeinsam genutzte Bibliotheken nach unterschiedlichen Regeln zu verknüpfen. Statische Bibliotheken in einer Verknüpfungssequenz wurden nach Bedarf in Beispiel 1 erläutert, gemeinsam genutzte Bibliotheken wurden jedoch unbedingt verknüpft.

Dieses Verhalten ist zur Geschäftszeit wirtschaftlich, da der Linker nicht darüber nachdenken muss, ob das Programm eine gemeinsam genutzte Bibliothek benötigt: Wenn es sich um eine gemeinsam genutzte Bibliothek handelt, verknüpfen Sie sie. Und die meisten Bibliotheken in den meisten Verknüpfungen sind gemeinsam genutzte Bibliotheken. Aber es gibt auch Nachteile:

  • Es ist zur Laufzeit unwirtschaftlich , da es dazu führen kann, dass gemeinsam genutzte Bibliotheken zusammen mit einem Programm geladen werden, auch wenn sie nicht benötigt werden.

  • Die unterschiedlichen Verknüpfungsregeln für statische und gemeinsam genutzte Bibliotheken können für unerfahrene Programmierer verwirrend sein, die möglicherweise nicht wissen, ob sich -lfooihre Verknüpfung zu /some/where/libfoo.aoder zu auflösen wird /some/where/libfoo.so, und den Unterschied zwischen gemeinsam genutzten und statischen Bibliotheken möglicherweise ohnehin nicht verstehen.

Dieser Kompromiss hat heute zu einer schismatischen Situation geführt. Einige Distributionen haben ihre GCC-Verknüpfungsregeln für gemeinsam genutzte Bibliotheken so geändert, dass das Prinzip nach Bedarf für alle Bibliotheken gilt. Einige Distributionen haben sich an den alten Weg gehalten.

Warum bekomme ich dieses Problem immer noch, auch wenn ich gleichzeitig kompiliere und verlinke?

Wenn ich es nur tue:

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

Natürlich muss gcc eg1.czuerst kompilieren und dann die resultierende Objektdatei mit verknüpfen libmy_lib.a. Wie kann es also nicht wissen, dass beim Verknüpfen eine Objektdatei benötigt wird?

Da das Kompilieren und Verknüpfen mit einem einzelnen Befehl die Reihenfolge der Verknüpfungssequenz nicht ändert.

Wenn Sie den obigen Befehl gccausführen, stellen Sie fest, dass Sie Kompilierung + Verknüpfung wünschen. Hinter den Kulissen wird ein Kompilierungsbefehl generiert und ausgeführt. Anschließend wird ein Verknüpfungsbefehl generiert und ausgeführt, als hätten Sie die beiden Befehle ausgeführt:

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

So ist die Verknüpfung nicht nur , wie es funktioniert , wenn Sie tun , diese beiden Befehle ausführen. Der einzige Unterschied, den Sie bei dem Fehler feststellen, besteht darin, dass gcc im Fall compile + link eine temporäre Objektdatei generiert hat, da Sie nicht die Verwendung anweisen eg1.o. Wir sehen:

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

anstatt:

eg1.o: In function `main':

Siehe auch

Die Reihenfolge, in der voneinander abhängige verknüpfte Bibliotheken angegeben werden, ist falsch

Das Versetzen von voneinander abhängigen Bibliotheken in die falsche Reihenfolge ist nur eine Möglichkeit, Dateien abzurufen, für die Definitionen von Dingen erforderlich sind, die später in der Verknüpfung erscheinen als die Dateien, die die Definitionen bereitstellen . Das Platzieren von Bibliotheken vor den Objektdateien, die auf sie verweisen, ist eine weitere Möglichkeit, denselben Fehler zu machen.

Mike Kinghan
quelle
18

Befreundende Vorlagen ...

Gegeben das Code-Snippet eines Vorlagentyps mit einem Freund-Operator (oder einer Friend-Funktion);

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

Das operator<<wird als Nicht-Vorlagenfunktion deklariert. Für jeden Typ, Tder mit verwendet wird Foo, muss eine Vorlage ohne Vorlage vorhanden sein operator<<. Wenn beispielsweise ein Typ Foo<int>deklariert ist, muss eine Operatorimplementierung wie folgt vorhanden sein.

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

Da es nicht implementiert ist, kann der Linker es nicht finden und führt zu dem Fehler.

Um dies zu korrigieren, können Sie einen Vorlagenoperator vor dem FooTyp deklarieren und dann als Freund die entsprechende Instanziierung deklarieren. Die Syntax ist etwas umständlich, sieht aber wie folgt aus:

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

Der obige Code beschränkt die Freundschaft des Betreibers auf die entsprechende Instanziierung von Foo, dh die operator<< <int>Instanziierung ist auf den Zugriff auf die privaten Mitglieder der Instanziierung von beschränkt Foo<int>.

Alternativen umfassen;

  • Zulassen, dass sich die Freundschaft wie folgt auf alle Instanziierungen der Vorlagen erstreckt;

    template <typename T>
    class Foo {
        template <typename T1>
        friend std::ostream& operator<<(std::ostream& os, const Foo<T1>& a);
        // ...
    };
  • Oder die Implementierung für operator<<kann inline innerhalb der Klassendefinition erfolgen.

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

Beachten Sie , dass, wenn die Deklaration des Operators (oder der Funktion) nur in der Klasse angezeigt wird, der Name nicht für die "normale" Suche verfügbar ist, sondern nur für die argumentabhängige Suche von cppreference .

Ein Name, der zuerst in einer Freundesdeklaration innerhalb der Klasse oder Klassenvorlage X deklariert wurde, wird Mitglied des innersten umschließenden Namespace von X, kann jedoch nur dann nachgeschlagen werden (mit Ausnahme der argumentabhängigen Suche, die X berücksichtigt), es sei denn, es liegt eine übereinstimmende Deklaration im Namespace-Bereich vor unter der Voraussetzung...

Es gibt weitere Informationen zu Vorlagenfreunden bei cppreference und the C ++ - FAQ .

Codeliste mit den oben genannten Techniken .


Als Randnotiz zum fehlerhaften Codebeispiel; g ++ warnt davor wie folgt

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
quelle
16

Wenn Ihre Include-Pfade unterschiedlich sind

Linker-Fehler können auftreten, wenn eine Header-Datei und die zugehörige gemeinsam genutzte Bibliothek (.lib-Datei) nicht mehr synchron sind. Lassen Sie mich erklären.

Wie funktionieren Linker? Der Linker vergleicht eine Funktionsdeklaration (im Header deklariert) mit ihrer Definition (in der gemeinsam genutzten Bibliothek), indem er ihre Signaturen vergleicht. Sie können einen Linkerfehler erhalten, wenn der Linker keine Funktionsdefinition findet, die perfekt übereinstimmt.

Ist es möglich, immer noch einen Linkerfehler zu erhalten, obwohl die Deklaration und die Definition übereinstimmen? Ja! Sie sehen im Quellcode vielleicht gleich aus, aber es hängt wirklich davon ab, was der Compiler sieht. Im Wesentlichen könnten Sie mit einer Situation wie dieser enden:

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

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

Beachten Sie, dass beide Funktionsdeklarationen im Quellcode zwar identisch aussehen, sich jedoch je nach Compiler stark unterscheiden.

Sie könnten fragen, wie man in einer solchen Situation landet? Natürlich auch Pfade einschließen ! Wenn beim Kompilieren der gemeinsam genutzten Bibliothek der Include-Pfad zu führt header1.hund Sie ihn letztendlich header2.hin Ihrem eigenen Programm verwenden, kratzen Sie sich am Header und fragen sich, was passiert ist (Wortspiel beabsichtigt).

Ein Beispiel, wie dies in der realen Welt geschehen kann, wird unten erläutert.

Weitere Ausarbeitung mit einem Beispiel

Ich habe zwei Projekte: graphics.libund main.exe. Beide Projekte hängen ab von common_math.h. Angenommen, die Bibliothek exportiert die folgende Funktion:

// graphics.lib    
#include "common_math.h" 

void draw(vec3 p) { ... } // vec3 comes from common_math.h

Und dann nehmen Sie die Bibliothek in Ihr eigenes Projekt auf.

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

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

Boom! Sie erhalten einen Linker-Fehler und wissen nicht, warum er fehlschlägt. Der Grund dafür ist, dass die gemeinsame Bibliothek unterschiedliche Versionen desselben Includes verwendetcommon_math.h (ich habe dies hier im Beispiel durch Einfügen eines anderen Pfads deutlich gemacht, aber es ist möglicherweise nicht immer so offensichtlich. Möglicherweise unterscheidet sich der Include-Pfad in den Compilereinstellungen). .

Beachten Sie in diesem Beispiel, dass der Linker Ihnen sagt, dass er nicht gefunden werden konnte draw(), wenn Sie in Wirklichkeit wissen, dass er offensichtlich von der Bibliothek exportiert wird. Sie könnten Stunden damit verbringen, sich am Kopf zu kratzen und sich zu fragen, was schief gelaufen ist. Die Sache ist, der Linker sieht eine andere Signatur, weil die Parametertypen leicht unterschiedlich sind. Im Beispiel vec3ist in beiden Projekten für den Compiler ein anderer Typ. Dies kann passieren, weil sie aus zwei leicht unterschiedlichen Include-Dateien stammen (möglicherweise stammen die Include-Dateien aus zwei verschiedenen Versionen der Bibliothek).

Debuggen des Linkers

DUMPBIN ist Ihr Freund, wenn Sie Visual Studio verwenden. Ich bin sicher, dass andere Compiler ähnliche Tools haben.

Der Prozess läuft folgendermaßen ab:

  1. Beachten Sie den seltsamen verstümmelten Namen, der im Linker-Fehler angegeben ist. (zB zeichnen Sie @ Grafiken @ XYZ).
  2. Speichern Sie die aus der Bibliothek exportierten Symbole in einer Textdatei.
  3. Suchen Sie nach dem exportierten Symbol von Interesse und stellen Sie fest, dass der verstümmelte Name unterschiedlich ist.
  4. Achten Sie darauf, warum die verstümmelten Namen anders ausgefallen sind. Sie können sehen, dass die Parametertypen unterschiedlich sind, obwohl sie im Quellcode gleich aussehen.
  5. Grund warum sie unterschiedlich sind. Im obigen Beispiel unterscheiden sie sich aufgrund unterschiedlicher Include-Dateien.

[1] Mit Projekt meine ich eine Reihe von Quelldateien, die miteinander verknüpft sind, um entweder eine Bibliothek oder eine ausführbare Datei zu erstellen.

EDIT 1: Der erste Abschnitt wurde neu geschrieben, um das Verständnis zu erleichtern. Bitte kommentieren Sie unten, um mich wissen zu lassen, ob etwas anderes repariert werden muss. Vielen Dank!

Fafaro
quelle
15

Inkonsistente UNICODEDefinitionen

Ein Windows UNICODE-Build wird erstellt, wobei TCHARusw. als wchar_tusw. definiert wird . Wenn nicht mit UNICODEdefiniert als Build mit TCHARdefiniert als charusw. erstellt wird. Diese UNICODEund Definitionen _UNICODEwirken sich auf alle " T" Zeichenfolgentypen aus . LPTSTR, LPCTSTRUnd ihr Elch.

Das Erstellen einer Bibliothek mit UNICODEdefinierten und der Versuch, sie in einem UNICODEnicht definierten Projekt zu verknüpfen, führt zu Linkerfehlern, da die Definition von nicht übereinstimmt TCHAR. charvs. wchar_t.

Der Fehler enthält normalerweise eine Funktion, einen Wert mit einem charoder einem wchar_tabgeleiteten Typ, diese können auch std::basic_string<>usw. enthalten. Beim Durchsuchen der betroffenen Funktion im Code wird häufig auf TCHARoder std::basic_string<TCHAR>usw. verwiesen . Dies ist ein Hinweis darauf, dass der Code ursprünglich sowohl für einen UNICODE- als auch für einen Multi-Byte-Zeichen- (oder "schmalen") Build gedacht war .

Um dies zu korrigieren, erstellen Sie alle erforderlichen Bibliotheken und Projekte mit einer konsistenten Definition von UNICODE(und _UNICODE).

  1. Dies kann mit beiden erfolgen;

    #define UNICODE
    #define _UNICODE
  2. Oder in den Projekteinstellungen;

    Projekteigenschaften> Allgemein> Projekteinstellungen> Zeichensatz

  3. Oder in der Kommandozeile;

    /DUNICODE /D_UNICODE

Die Alternative ist auch anwendbar, wenn UNICODE nicht verwendet werden soll, stellen Sie sicher, dass die Definitionen nicht festgelegt sind und / oder die Einstellung für mehrere Zeichen in den Projekten verwendet und konsistent angewendet wird.

Vergessen Sie nicht, auch zwischen den Builds "Release" und "Debug" konsistent zu sein.

Niall
quelle
14

Reinigen und wieder aufbauen

Eine "Bereinigung" des Builds kann das "tote Holz" entfernen, das möglicherweise von früheren Builds, fehlgeschlagenen Builds, unvollständigen Builds und anderen Build-System-bezogenen Build-Problemen übrig geblieben ist.

Im Allgemeinen enthält die IDE oder der Build eine Form der "sauberen" Funktion, die jedoch möglicherweise nicht korrekt konfiguriert ist (z. B. in einem manuellen Makefile) oder fehlschlägt (z. B. sind die Zwischen- oder resultierenden Binärdateien schreibgeschützt).

Überprüfen Sie nach Abschluss der "Bereinigung", ob die "Bereinigung" erfolgreich war und alle generierten Zwischendateien (z. B. ein automatisiertes Makefile) erfolgreich entfernt wurden.

Dieser Prozess kann als letzter Ausweg angesehen werden, ist aber oft ein guter erster Schritt . insbesondere, wenn der mit dem Fehler verbundene Code kürzlich hinzugefügt wurde (entweder lokal oder aus dem Quell-Repository).

Niall
quelle
10

Fehlendes "extern" in constVariablendeklarationen / -definitionen (nur C ++)

Für Leute, die aus C kommen, könnte es eine Überraschung sein, dass globale constVariablen in C ++ eine interne (oder statische) Verknüpfung haben. In C war dies nicht der Fall, da alle globalen Variablen implizit sind extern(dh wenn das staticSchlüsselwort fehlt).

Beispiel:

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

Richtig wäre es, eine Header-Datei zu verwenden und sie in file2.cpp und file1.cpp aufzunehmen

extern const int test;
extern int test2;

Alternativ könnte man die constVariable in file1.cpp mit explizit deklarierenextern

Andreas H.
quelle
8

Obwohl dies eine ziemlich alte Frage mit mehreren akzeptierten Antworten ist, möchte ich Ihnen mitteilen, wie Sie einen obskuren "undefinierten Verweis auf" -Fehler beheben können .

Verschiedene Versionen von Bibliotheken

Ich habe einen Alias ​​verwendet, um auf std::filesystem::pathFolgendes zu verweisen : Das Dateisystem befindet sich seit C ++ 17 in der Standardbibliothek, aber mein Programm musste auch in C ++ 14 kompiliert werden. Daher habe ich beschlossen, einen variablen Alias ​​zu verwenden:

#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

Angenommen, ich habe drei Dateien: main.cpp, file.h, file.cpp:

  • file.h # include < experimentelles :: Dateisystem > und enthält den obigen Code
  • file.cpp , die Implementierung von file.h, # include's " file.h "
  • main.cpp # include's < Dateisystem > und " file.h "

Beachten Sie die verschiedenen Bibliotheken, die in main.cpp und file.h verwendet werden. Da main.cpp # " file.h " nach < Dateisystem > enthält, war die dort verwendete Version des Dateisystems die C ++ 17- Version . Ich habe das Programm mit den folgenden Befehlen kompiliert:

$ g++ -g -std=c++17 -c main.cpp-> kompiliert main.cpp zu main.o
$ g++ -g -std=c++17 -c file.cpp-> kompiliert file.cpp und file.h zu file.o
$ g++ -g -std=c++17 -o executable main.o file.o -lstdc++fs-> verknüpft main.o und file.o

Auf diese Weise jede Funktion enthalten in file.o und in main.o verwendet , dass erforderlichpath_t „undefined reference“ Fehler gab , weil main.o bezeichnet std::filesystem::pathaber file.o zu std::experimental::filesystem::path.

Auflösung

Um dies zu beheben, musste ich nur <experimentelles :: Dateisystem> in file.h in <Dateisystem> ändern .

Stypox
quelle
5

Stellen Sie beim Verknüpfen mit gemeinsam genutzten Bibliotheken sicher, dass die verwendeten Symbole nicht ausgeblendet sind.

Das Standardverhalten von gcc ist, dass alle Symbole sichtbar sind. Wenn die Übersetzungseinheiten jedoch mit einer Option erstellt werden -fvisibility=hidden, sind nur Funktionen, die mit gekennzeichnet __attribute__ ((visibility ("default")))sind, im resultierenden gemeinsam genutzten Objekt extern.

Sie können überprüfen, ob die gesuchten Symbole extern sind, indem Sie Folgendes aufrufen:

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

Die versteckten / lokalen Symbole werden nmmit einem Symboltyp in Kleinbuchstaben angezeigt , z. B. tanstelle von `T für den Codeabschnitt:

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

Sie können auch nmmit der Option verwenden -C, um die Namen zu entwirren (wenn C ++ verwendet wurde).

Ähnlich wie bei Windows-DLLs würde man öffentliche Funktionen mit einer Definition markieren, zum Beispiel DLL_PUBLICdefiniert als:

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

DLL_PUBLIC int my_public_function(){
  ...
}

Was in etwa der Windows / MSVC-Version entspricht:

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

Weitere Informationen zur Sichtbarkeit finden Sie im gcc-Wiki.


Wenn eine Übersetzungseinheit mit -fvisibility=hiddenden resultierenden Symbolen kompiliert wird, haben sie immer noch eine externe Verknüpfung (angezeigt mit dem Symboltyp Großbuchstaben von nm) und können problemlos für die externe Verknüpfung verwendet werden, wenn die Objektdateien Teil einer statischen Bibliothek werden. Die Verknüpfung wird nur dann lokal, wenn die Objektdateien mit einer gemeinsam genutzten Bibliothek verknüpft sind.

So finden Sie heraus, welche Symbole in einer Objektdatei ausgeblendet sind:

>>> objdump -t XXXX.o | grep hidden
0000000000000000 g     F .text  000000000000000b .hidden HIDDEN_SYMBOL1
000000000000000b g     F .text  000000000000000b .hidden HIDDEN_SYMBOL2
ead
quelle
Sie sollten nm -CDoder verwenden nm -gCD, um externe Symbole anzuzeigen. Siehe auch Sichtbarkeit im GCC-Wiki.
JWW
2

Unterschiedliche Architekturen

Möglicherweise sehen Sie eine Nachricht wie:

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

In diesem Fall bedeutet dies, dass die verfügbaren Symbole für eine andere Architektur gelten als die, für die Sie kompilieren.

In Visual Studio liegt dies an der falschen "Plattform", und Sie müssen entweder die richtige auswählen oder die richtige Version der Bibliothek installieren.

Unter Linux kann dies an dem falschen Bibliotheksordner liegen ( z. B. libanstelle von lib64).

Unter MacOS besteht die Möglichkeit, beide Architekturen in derselben Datei zu versenden. Es kann sein, dass der Link erwartet, dass beide Versionen vorhanden sind, aber nur eine. Es kann auch ein Problem mit dem falschen lib/ lib64Ordner sein, in dem die Bibliothek abgerufen wird.

Matthieu Brucher
quelle