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.
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.
Antworten:
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] .
- Die Zeichen der physischen Quelldatei werden bei Bedarf implementierungsdefiniert dem grundlegenden Quellzeichensatz zugeordnet (Einführung neuer Zeilenzeichen für Zeilenende-Indikatoren). [SNIP]
- Jede Instanz eines Backslash-Zeichens (\), unmittelbar gefolgt von einem neuen Zeilenzeichen, wird gelöscht, wodurch physische Quellzeilen zu logischen Quellzeilen gespleißt werden. [SNIP]
- Die Quelldatei wird in Vorverarbeitungstoken (2.5) und Sequenzen von Leerzeichen (einschließlich Kommentaren) zerlegt. [SNIP]
- Vorverarbeitungsanweisungen werden ausgeführt, Makroaufrufe werden erweitert und _Pragma-unäre Operatorausdrücke werden ausgeführt. [SNIP]
- 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]
- Benachbarte String-Literal-Token werden verkettet.
- 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]
- Übersetzte Übersetzungseinheiten und Instanziierungseinheiten werden wie folgt kombiniert: [SNIP]
- 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 a
in 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 .lib
Dateien 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 LNK2019
fü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:
#pragma
(Microsoft Visual Studio)UNICODE
Definitionenvirtual
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- virtual
Methoden 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;
};
virtual
KlassenmitgliederEinige 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.
struct A
{
void foo();
};
void foo() {}
int main()
{
A a;
a.foo();
}
Die Definition sollte sein
void A::foo() {}
static
Datenelemente 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
const
Datenelement 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 const
Datenelemente.
Ü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 libraryName
hier ist nur der bloße Name der Bibliothek ohne plattformspezifische Ergänzungen. So werden zB unter Linux normalerweise Bibliotheksdateien aufgerufen, libfoo.so
aber 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 -l
oder 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 lib
Datei generiert (im allgemeinen Sprachgebrauch). Um die Symbole in einem separaten Projekt zu verwenden, müssen Sie die lib
Dateien in die Projekteinstellungen aufnehmen. Dies erfolgt im Abschnitt Linker der Projekteigenschaften in Input -> Additional Dependencies
. (Der Pfad zur lib
Datei 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.
gcc main.c
statt behandeln könnten gcc main.c other.c
(was Anfänger oft tun, bevor ihre Projekte so groß werden, dass sie .o-Dateien erstellen).
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
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.
#includes
nicht zum Quellverzeichnis hinzugefügt wurden , ebenfalls unter die Kategorie der fehlenden Definitionen.
Die Reihenfolge, in der Bibliotheken verknüpft sind, spielt eine Rolle, wenn die Bibliotheken voneinander abhängen. Wenn die Bibliothek A
von der Bibliothek abhängt B
, libA
MUSS sie libB
im 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!
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.
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"
}
#ifdef __cplusplus [\n] extern"C" { [\n] #endif
und umgeben #ifdef __cplusplus [\n] } [\n] #endif
(dies [\n]
ist ein echter Wagenrücklauf, aber ich kann dies nicht richtig in einen Kommentar schreiben).
extern "C" { #include <myCppHeader.h> }
.
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.
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_MODULE
nur 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
{
};
visibility
die .def
Dateien von GCC und Windows erwähnt werden, da diese auch den Symbolnamen und die Präsenz beeinflussen.
.def
Dateien mehr verwendet . Fühlen Sie sich frei, eine Antwort hinzuzufügen oder diese zu bearbeiten.
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:
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:
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.
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 Dependency
Ihrer Projekteinstellung hinzugefügt wird . Jetzt meldet der Linker einen LNK2019, da die Definition im aktuellen Suchbereich nicht vorhanden ist.
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 impl
Datei 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 .
undefinierter Verweis auf WinMain@16
oder ähnliche 'ungewöhnliche' main()
Einstiegspunktreferenz (insbesondere fürVisual-Studio).
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 .
WinMain
. Gültige C ++ - Programme benötigen a main
.
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 packages
Ordner im Verzeichnis der Lösung) bearbeitet, indem ich packagename\build\native\packagename.targets
in dieser Datei alle v110
Abschnitte gefunden und kopiert habe . Ich änderte die v110
auf v120
in 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.
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?
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
error LNK2001: unresolved external symbol "void __cdecl foo(void)" (?foo@@YAXXZ)
Überwachung
So lösen Sie diese Art von Fehler
Compiler-Zeitfehler:
Linker-Zeitfehler
#pragma once
Sie diese Option, um dem Compiler zu erlauben, keinen Header einzuschließen, wenn dieser bereits in der aktuellen kompilierten CPP enthalten warIch 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:
Die meisten modernen Linker enthalten eine ausführliche Option, die in unterschiedlichem Maße gedruckt wird.
Für gcc und clang; Sie würden normalerweise -v -Wl,--verbose
oder -v -Wl,-v
zur Befehlszeile hinzufügen . Weitere Details finden Sie hier;
Bei MSVC wird /VERBOSE
(insbesondere /VERBOSE:LIB
) der Link-Befehlszeile hinzugefügt.
/VERBOSE
Linker-Option .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.
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.
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 -ltbb
do/home/foo/tbb-4.3/linux/lib/intel64/gcc4.4/libtbb.so.2
libfoo
ankommt libbar
, dann setzt Ihre Verknüpfung richtig libfoo
vor libbar
.undefined reference to
etwas Fehlern.#include
d und sind in der Tat in den Bibliotheken definiert , die Sie verknüpfen.Beispiele sind in C. Sie könnten genauso gut C ++ sein
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.a
und 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
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, libz
und 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'
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-config
Variante von Beispiel 2 richtig:
$ gcc -o eg2 eg2.o $(pkg-config --libs zlib)
$ ./eg2
1.2.8
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_lib
in 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.o
genau dann benötigt, wenn er bereits weiß, dass Ihr Programm auf hw
eine 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.o
aus der Bibliothek und fügt sie Ihrem Programm hinzu. Dann enthält das Programm eine Definition für hw
, so seine Verweise auf hw
sind 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.o
in das Programm ein und hat keine weitere Verwendung für libmy_lib.a
.
Als nächstes wird es gefunden eg1.o
und als Programm hinzugefügt. Eine Objektdatei in der Verknüpfungssequenz wird immer zum Programm hinzugefügt. Das Programm verweist nun auf hw
und 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 hw
wird 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 main
Funktion 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++
, gfortran
usw.) 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 -lz
in 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 libz
und fügen Sie sie in Ihrem Programm; Stattdessen wird nur der Code Ihres Programms behandelt, so dass: -
Zur Laufzeit libz
lä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 libz
im 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-config
Variation 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.
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 -lfoo
ihre Verknüpfung zu /some/where/libfoo.a
oder 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.
Wenn ich es nur tue:
$ gcc -o eg1 -I. -L. -lmy_lib eg1.c
Natürlich muss gcc eg1.c
zuerst 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 gcc
ausfü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':
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.
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, T
der 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 Foo
Typ 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)
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.h
und Sie ihn letztendlich header2.h
in 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.
Ich habe zwei Projekte: graphics.lib
und 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 vec3
ist 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).
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] 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!
UNICODE
DefinitionenEin Windows UNICODE-Build wird erstellt, wobei TCHAR
usw. als wchar_t
usw. definiert wird . Wenn nicht mit UNICODE
definiert als Build mit TCHAR
definiert als char
usw. erstellt wird. Diese UNICODE
und Definitionen _UNICODE
wirken sich auf alle " T
" Zeichenfolgentypen aus . LPTSTR
, LPCTSTR
Und ihr Elch.
Das Erstellen einer Bibliothek mit UNICODE
definierten und der Versuch, sie in einem UNICODE
nicht definierten Projekt zu verknüpfen, führt zu Linkerfehlern, da die Definition von nicht übereinstimmt TCHAR
. char
vs. wchar_t
.
Der Fehler enthält normalerweise eine Funktion, einen Wert mit einem char
oder einem wchar_t
abgeleiteten Typ, diese können auch std::basic_string<>
usw. enthalten. Beim Durchsuchen der betroffenen Funktion im Code wird häufig auf TCHAR
oder 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
).
Dies kann mit beiden erfolgen;
#define UNICODE
#define _UNICODE
Oder in den Projekteinstellungen;
Projekteigenschaften> Allgemein> Projekteinstellungen> Zeichensatz
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.
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).
const
Variablendeklarationen / -definitionen (nur C ++)Für Leute, die aus C kommen, könnte es eine Überraschung sein, dass globale const
Variablen 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 static
Schlü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 const
Variable in file1.cpp mit explizit deklarierenextern
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 .
Ich habe einen Alias verwendet, um auf std::filesystem::path
Folgendes 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:
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::path
aber file.o zu std::experimental::filesystem::path
.
Um dies zu beheben, musste ich nur <experimentelles :: Dateisystem> in file.h in <Dateisystem> ändern .
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 nm
mit einem Symboltyp in Kleinbuchstaben angezeigt , z. B. t
anstelle von `T für den Codeabschnitt:
nm XXX.so
00000000000005a7 t HIDDEN_SYMBOL
00000000000005f8 T VISIBLE_SYMBOL
Sie können auch nm
mit 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_PUBLIC
definiert 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=hidden
den 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
nm -CD
oder verwenden nm -gCD
, um externe Symbole anzuzeigen. Siehe auch Sichtbarkeit im GCC-Wiki.
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. lib
anstelle 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
/ lib64
Ordner sein, in dem die Bibliothek abgerufen wird.