Was sind undefinierte Verweise/nicht aufgelöste externe Symbolfehler? Was sind häufige Ursachen und wie lassen sich diese beheben?
Fühlen Sie sich frei, Ihre eigenen zu bearbeiten/hinzufügen.
Das Kompilieren eines C++ - Programms erfolgt in mehreren Schritten, wie durch 2.2angegeben (für die Referenz Keith Thompson) :
Der Vorrang unter den Syntaxregeln für die Übersetzung wird durch die folgenden Phasen angegeben: [siehe Fußnote].
- Physische Quelldateizeichen werden implementierungsdefiniert dem grundlegenden Quellzeichensatz .__ zugeordnet. (Einführung neuer Zeilenzeichen für Zeilenendeindikatoren) if notwendig. [SNIP]
- Jede Instanz eines Backslash-Zeichens (\) unmittelbar gefolgt von einem Zeichen für eine neue Zeile wird gelöscht, wodurch die physischen Quellzeilen mit .__ verbunden werden. logische Quellzeilen bilden. [SNIP]
- Die Quelldatei wird in Vorverarbeitungstoken (2.5) und Sequenzen von Leerzeichen (einschließlich Kommentaren) zerlegt. [SNIP]
- Vorbearbeitungsdirektiven werden ausgeführt, Makroaufrufe werden erweitert und unäre Operatorausdrücke _Pragma werden ausgeführt. [SNIP]
- Jeder Quellzeichensatz-Member in einem Zeichenliteral oder einem Zeichenfolgenliteral sowie jede Escape-Sequenz und der universelle Zeichenname Wird in einem Zeichenliteral oder einem nicht reinen Zeichenfolgenliteral in .__ konvertiert. das entsprechende Mitglied des Ausführungszeichensatzes; [SNIP]
- Angrenzende String-Literal-Token werden verkettet.
- Leerzeichen, die Token trennen, sind nicht mehr von Bedeutung. Jedes Vorverarbeitungstoken wird in ein Token umgewandelt. (2.7). Das 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ätsverweise werden aufgelöst. Bibliothekskomponenten sind verknüpft, um externe Verweise auf Entitäten zu erfüllen, die nicht in .__ definiert sind. aktuelle Übersetzung. Alle derartigen Übersetzerausgaben werden in einem .__ gesammelt. Programmabbild, das Informationen enthält, die zur Ausführung in seiner .__ benötigt werden. Ausführungsumgebung. (Hervorhebung meines)
[footnote] Implementierungen müssen sich so verhalten, als ob diese separaten Phasen auftreten würden, obwohl in der Praxis verschiedene Phasen zusammengelegt werden könnten.
Die angegebenen Fehler treten in dieser letzten Phase der Kompilierung auf, am häufigsten als Verknüpfung bezeichnet. Im Grunde bedeutet dies, dass Sie eine Reihe von Implementierungsdateien in Objektdateien oder Bibliotheken kompiliert haben, und nun möchten Sie, dass sie zusammenarbeiten.
Angenommen, Sie haben das Symbol a
in a.cpp
definiert. Nun b.cpp
deklariert dieses Symbol und verwendete es. Vor dem Verknüpfen wird lediglich davon ausgegangen, dass dieses Symbol irgendwo definiert wurde, aber es ist noch nicht wichtig, wo es liegt. Die Verknüpfungsphase ist dafür verantwortlich, das Symbol zu finden und es korrekt mit b.cpp
zu verknüpfen (tatsächlich mit dem Objekt oder der Bibliothek, das 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 anhand der Bibliotheken aufgelöst, mit denen Sie verknüpfen, und die exportierten Symbole werden für die Bibliotheken bereitgestellt, die diesen .lib
verwenden (sofern vorhanden).
Ähnliche Mechanismen existieren für andere Compiler/Plattformen.
Häufige Fehlermeldungen sind error LNK2001
, error LNK1120
, error LNK2019
für Microsoft Visual Studio und undefined reference to
symbolName 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;
}
erzeugt folgende 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)" ([email protected]@YAXXZ)
1>test2.obj : error LNK2001: unresolved external symbol "int x" ([email protected]@3HA)
1>test2.obj : error LNK2001: unresolved external symbol "public: virtual __thiscall A::~A(void)" ([email protected]@[email protected])
1>test2.obj : error LNK2001: unresolved external symbol "public: virtual void __thiscall X::foo(void)" ([email protected]@@UAEXXZ)
1>...\test2.exe : fatal error LNK1120: 4 unresolved externals
Häufige Ursachen sind:
#pragma
(Microsoft Visual Studio) ist die Erweiterung .lib falsch geschrieben oder nicht enthaltenUNICODE
-Definitionenvirtual
-Destruktor benötigt eine Implementierung.Um einen Destruktor als pure 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 geschieht, weil Destruktoren der Basisklasse aufgerufen werden, wenn das Objekt implizit zerstört wird. Daher ist eine Definition erforderlich.
virtual
-Methoden müssen entweder implementiert oder als pure definiert sein.Dies ist vergleichbar mit non -virtual
-Methoden ohne Definition, mit der zusätzlichen Begründung, dass die reine Deklaration eine Dummy-vtable generiert und der Linker-Fehler möglicherweise ohne Verwendung der Funktion angezeigt wird:
struct X
{
virtual void foo();
};
struct Y : X
{
void foo() {}
};
int main()
{
Y y; //linker error although there was no call to X::foo
}
Um dies zu erreichen, erklären 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 erfolgen:
struct A
{
~A() {}
};
oder außerhalb:
A::~A() {}
Wenn sich die Implementierung außerhalb der Klassendefinition befindet, jedoch in einem Header, müssen die Methoden als inline
markiert sein, um eine Mehrfachdefinition zu verhindern.
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 einzelnen Übersetzungseinheit definiert sein :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 einen static
const
-Datenmember vom Integral- oder Aufzählungstyp innerhalb der Klassendefinition bereitgestellt werden. Für die odr-Verwendung dieses Members 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.
Normalerweise generiert jede Übersetzungseinheit eine Objektdatei, die die Definitionen der in dieser Übersetzungseinheit definierten Symbole enthält. Um diese Symbole verwenden zu können, 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 zusammenstellen.
g++ -o test objectFile1.o objectFile2.o -lLibraryName
Die libraryName
ist hier nur der bloße Name der Bibliothek ohne plattformspezifische Zusätze. So z. Unter Linux heißen Bibliotheksdateien normalerweise libfoo.so
, aber Sie schreiben nur -lfoo
. Unter Windows könnte dieselbe Datei foo.lib
heißen, aber Sie würden dasselbe Argument verwenden. Möglicherweise müssen Sie das Verzeichnis hinzufügen, in dem diese Dateien mit -L‹directory›
gefunden werden können. Stellen Sie sicher, dass nach -l
oder -L
kein Leerzeichen geschrieben wird.
Für XCode : Hinzufügen der Suchpfade für die Benutzerkopfzeile -> Hinzufügen des Bibliotheksuchpfads -> Ziehen Sie den aktuellen Bibliotheksverweis in den Projektordner und legen Sie ihn dort ab.
UnterMSVSwerden Dateien, die zu einem Projekt hinzugefügt werden, automatisch miteinander verknüpft, und es wird eine lib
-Datei generiert (häufig verwendet). Um die Symbole in einem separaten Projekt verwenden zu können, müssen Sie die Dateien lib
in die Projekteinstellungen aufnehmen. Dies erfolgt im Linker-Abschnitt der Projekteigenschaften in Input -> Additional Dependencies
. (Der Pfad zur lib
-Datei sollte in Linker -> General -> Additional Library Directories
hinzugefügt werden.) Wenn Sie eine Fremdanbieter-Bibliothek verwenden, die mit einer lib
-Datei ausgestattet ist, führt dies normalerweise zu einem Fehler.
Es kann auch vorkommen, dass Sie vergessen, die Datei der Kompilierung hinzuzufügen. In diesem Fall wird die Objektdatei nicht generiert. In gcc fügen Sie die Dateien zur Befehlszeile hinzu. Wenn Sie die Datei inMSVSzum Projekt hinzufügen, wird sie automatisch kompiliert (obwohl Dateien manuell vom Build ausgeschlossen werden können).
Bei der Windows-Programmierung besteht das verräterische Zeichen, dass Sie eine erforderliche Bibliothek nicht verknüpft haben, darin, dass der Name des nicht aufgelösten Symbols mit __imp_
beginnt. Suchen Sie in der Dokumentation nach dem Namen der Funktion. Sie sollte angeben, welche Bibliothek Sie verwenden müssen. Beispielsweise stellt MSDN die Informationen in einem Feld am unteren Rand jeder Funktion in einem Abschnitt mit dem Namen "Library".
Eine typische Variablendeklaration ist
extern int x;
Da dies nur eine Deklaration ist, ist eine Einzeldefinition erforderlich. Eine entsprechende Definition wäre:
int x;
Beispielsweise würde Folgendes 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 zum 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 der von Ihnen definierten entspricht. Beispielsweise haben Sie möglicherweise nicht übereinstimmende CV-Qualifikationsmerkmale:
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 Fehlanpassungen umfassen
In der Fehlermeldung des Compilers erhalten Sie häufig die vollständige Deklaration der Variablen oder Funktion, die zwar deklariert, aber nie definiert wurde. Vergleichen Sie es eng mit der von Ihnen angegebenen Definition. Stellen Sie sicher, dass jedes Detail übereinstimmt.
Die Reihenfolge, in der Bibliotheken verknüpft sind, spielt eine Rolle, wenn die Bibliotheken voneinander abhängig sind. Wenn die Bibliothek A
im Allgemeinen von der Bibliothek B
abhängt, muss libA
vor libB
in den Linker-Flags erscheinen.
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
Also nochmal wiederholen, die ReihenfolgeMACHTegal!
Was ist eine "undefinierte Referenz/ein nicht gelöstes externes Symbol"
Ich werde versuchen zu erklären, was ein "undefiniertes Verweis/nicht aufgelöstes externes Symbol" ist.
anmerkung: Ich verwende g ++ und Linux und alle Beispiele sind dafür
Zum Beispiel haben wir etwas 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. Sehen Sie sich 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 zurückgewiesen, 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 Symbole gesehen
Verknüpfen Sie unsere Objektdateien
$ g++ src1.o src2.o -o prog
und führe es aus
$ ./prog
123
Linker sieht exportierte Symbole und verlinkt diese. Jetzt versuchen wir, Zeilen in src2.cpp wie hier auszukommentieren
// 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 eine Objektdatei neu erstellen
$ g++ -c src2.cpp -o src2.o
OK (keine Fehler), da wir nur eine Objektdatei erstellen, ist das Verknüpfen noch nicht abgeschlossen
$ 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 der Fall, weil unser local_var_name statisch ist, d. H. Für andere Module nicht sichtbar ist. Jetzt tiefer. Holen Sie sich die Ü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 gesehen, dass local_var_name kein Label hat. Deshalb hat linker es nicht gefunden. Aber wir sind Hacker :) und können das Problem 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
d. h. Sie sollten 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, eine Objektdatei daraus zu erstellen
$ g++ -c src1.s -o src2.o
ok, siehe Readelf-Ausgabe (Symbole)
$ readelf --symbols src1.o
8: 0000000000000000 4 OBJECT GLOBAL DEFAULT 3 local_var_name
local_var_name hat jetzt Bind GLOBAL (war LOCAL)
verknüpfung
$ g++ src1.o src2.o -o prog
und führe es aus
$ ./prog
123456789
ok, wir hacken es :)
Als Ergebnis tritt ein "undefinierter Verweis/nicht aufgelöster externer Symbolfehler" auf, wenn der Linker keine globalen Symbole in den Objektdateien findet.
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 manipuliert werden. Daher müssen Sie die Funktion folgendermaßen definieren:
extern "C" void foo();
int main()
{
foo();
}
Äquivalent wurde die Funktion (oder Variable) void foo()
in C++ definiert, jedoch nicht mit einem C-Programm, sondern 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"
}
Wenn alles andere fehlschlägt, kompilieren Sie erneut.
Ich konnte vor kurzem einen nicht aufgelösten externen Fehler in Visual Studio 2012 einfach dadurch entfernen, dass ich die fehlerhafte Datei neu kompilierte. Wenn ich neu baute, ging der Fehler weg.
Dies ist normalerweise der Fall, wenn zwei (oder mehr) Bibliotheken eine zyklische Abhängigkeit haben. Bibliothek A versucht, Symbole in B.lib zu verwenden, und Bibliothek B versucht, Symbole aus A.lib zu verwenden. Es gibt keine, um mit zu beginnen. Wenn Sie versuchen, A zu kompilieren, schlägt der Link-Schritt fehl, da er B.lib nicht finden kann. A.lib wird generiert, aber keine DLL. Sie kompilieren dann B, was erfolgreich sein wird, und generiert B.lib. A neu kompilieren wird jetzt funktionieren, da nun B.lib gefunden wird.
Für MSVS müssen Sie angeben, welche Symbole mit __declspec(dllexport)
und __declspec(dllimport)
exportiert und importiert werden sollen.
Diese doppelte Funktionalität wird normalerweise durch die Verwendung eines Makros erzielt:
#ifdef THIS_MODULE
#define DLLIMPEXP __declspec(dllexport)
#else
#define DLLIMPEXP __declspec(dllimport)
#endif
Das Makro THIS_MODULE
würde nur in dem Modul definiert, das die Funktion exportiert. Auf diese Weise die Erklärung:
DLLIMPEXP void foo();
erweitert zu
__declspec(dllexport) void foo();
und weist den Compiler an, die Funktion zu exportieren, da das aktuelle Modul seine Definition enthält. Wenn Sie die Deklaration in ein anderes Modul aufnehmen, wird sie erweitert
__declspec(dllimport) void foo();
und teilt dem Compiler mit, dass sich die Definition in einer der Bibliotheken befindet, mit denen Sie verlinkt haben (siehe auch 1) ).
Sie können ähnliche Klassen importieren/exportieren:
class DLLIMPEXP X
{
};
Dies ist eine der verwirrendsten Fehlermeldungen, die jeder VC++ - Programmierer immer und immer wieder gesehen hat. Machen wir zuerst Klarheit.
A. Was ist ein Symbol? Kurz gesagt, ein Symbol ist ein Name. Dies kann ein Variablenname, ein Funktionsname, ein Klassenname, ein Typedef-Name oder ein beliebiger Name sein, mit Ausnahme der Namen und Zeichen, die zur C++ - Sprache gehören. Es ist benutzerdefiniert oder wird 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 Übersetzung Einheit. (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)
usw. verwenden.
C. Was ist "Entschlossenheit"? Auflösen ist ein Begriff der Verknüpfungszeit. In der Verbindungszeit versucht der Linker, die externe Definition für jedes Symbol in Objektdateien zu finden, das seine Definition nicht intern finden kann. Der Umfang dieses Suchprozesses umfasst:
Dieser Suchvorgang wird als Auflösen bezeichnet.
D. Warum noch nicht gelöstes externes Symbol? .__ Wenn der Linker die externe Definition für ein Symbol, das intern keine Definition hat, nicht finden kann, meldet er den Fehler "Nicht aufgelöstes externes Symbol".
E. Mögliche Ursachen für LNK2019 : Fehler beim Beheben eines externen Symbols . Wir wissen bereits, dass dieser Fehler darauf zurückzuführen ist, dass der Linker die Definition externer Symbole nicht gefunden hat.
Wenn zum Beispiel eine Funktion namens foo in a.cpp definiert ist:
int foo()
{
return 0;
}
In b.cpp wollen wir die Funktion foo aufrufen, also hinzufügen
void foo();
um die Funktion foo () zu deklarieren und sie in einem anderen Funktionskörper aufzurufen, sagen Sie bar()
:
void bar()
{
foo();
}
Wenn Sie diesen Code erstellen, wird jetzt ein LNK2019-Fehler angezeigt, der besagt, dass foo ein nicht aufgelö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, dass Definition existiert.
Wenn Sie einige Funktionen in einer Bibliothek aufrufen möchten, die Importbibliothek jedoch nicht der zusätzlichen Abhängigkeitsliste (festgelegt von: Project | Properties | Configuration Properties | Linker | Input | Additional Dependency
) Ihrer Projekteinstellung hinzugefügt wird. Der Linker meldet jetzt ein LNK2019, da die Definition nicht im aktuellen Suchbereich vorhanden ist.
Für nicht spezialisierte Vorlagen müssen ihre Definitionen für alle Übersetzungseinheiten sichtbar sein, die sie verwenden. Das bedeutet, dass Sie die Definition einer Vorlage nicht in eine Implementierungsdatei trennen können. Wenn Sie die Implementierung trennen müssen, besteht die übliche Problemumgehung darin, eine impl
-Datei zu haben, die Sie am Ende des Headers einfügen, die die Vorlage mit. 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 X::foo
in die Header-Datei oder an eine andere Stelle verschieben, die für die Übersetzungseinheit sichtbar ist, die sie verwendet.
Spezialisierte Vorlagen können in eine Implementierungsdatei implementiert werden, und die Implementierung muss nicht sichtbar sein. Die Spezialisierung muss jedoch zuvor deklariert werden.
Für weitere Erklärungen und eine andere mögliche Lösung (explizite Instantiierung) siehe diese Frage und Antwort .
undefinierte Referenz auf [email protected]
oder ähnliche 'ungewöhnlich'main()
Einstiegspunktreferenz (insbesondere für visual-studio ).
Möglicherweise haben Sie es versäumt, den richtigen Projekttyp mit Ihrer tatsächlichen IDE auszuwählen. Die IDE möchte z. Windows-Anwendungsprojekte für eine solche Einstiegspunktfunktion (wie in der fehlenden Referenz oben angegeben) anstelle der häufig verwendeten int main(int argc, char** argv);
-Signatur.
Wenn Ihr IDE Plain Console-Projekte unterstützt, möchten Sie möglicherweise diesen Projekttyp anstelle eines Windows-Anwendungsprojekts auswählen.
Hier werden case1 und case2 ausführlicher von einem realen _ Problem behandelt.
Wenn Sie Drittanbieter-Bibliotheken verwenden, stellen Sie sicher, dass Sie über die korrekten 32/64-Bit-Binärdateien verfügen
Microsoft bietet einen #pragma
an, um zur Linkzeit auf die richtige Bibliothek zu verweisen.
#pragma comment(lib, "libname.lib")
Neben dem Bibliothekspfad einschließlich des Verzeichnisses der Bibliothek sollte dies der vollständige Name der Bibliothek sein.
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 hatte.
Die richtige Lösung ist zu hoffen, dass der Entwickler ein aktualisiertes Paket und dann ein Upgrade freigibt, aber es funktionierte für mich, indem er eine zusätzliche Einstellung für VS2013 angriff und auf die VS2012-Bibliotheksdateien zeigte.
Ich habe das Paket (im Ordner packages
im Verzeichnis der Lösung) bearbeitet, indem ich packagename\build\native\packagename.targets
in dieser Datei gefunden und alle v110
-Abschnitte kopiert habe. Ich änderte den v110
in v120
in nur die Bedingungsfelder und achtete sehr darauf, die Dateinamenpfade alle als v110
zu belassen. Dadurch konnte Visual Studio 2013 einfach eine Verknüpfung zu den Bibliotheken für 2012 herstellen. In diesem Fall funktionierte es.
Angenommen, Sie haben ein großes Projekt in c ++ geschrieben, das aus Tausend .cpp-Dateien und aus Tausend .h-Dateien besteht. Und das Projekt hängt auch von zehn statischen Bibliotheken ab. Nehmen wir an, wir sind unter Windows und bauen unser Projekt in Visual Studio 20xx auf. Wenn Sie die Tastenkombination Strg + F7 Visual Studio drücken, um die gesamte Lösung zu kompilieren (angenommen, es gibt nur ein Projekt in der Lösung)
Was bedeutet Compilierung?
Der zweite Schritt der Kompilierung wird von Linker ausgeführt. Der Linker sollte alle Objektdateien zusammenführen und schließlich die Ausgabe erstellen (die eine ausführbare Datei oder eine Bibliothek sein kann).
Schritte beim Verknüpfen eines Projekts
error LNK2001: unresolved external symbol "void __cdecl foo(void)" ([email protected]@YAXXZ)
anhören kann.Überwachung
So lösen Sie diese Art von Fehler
Compiler-Zeitfehler:
Linker-Zeitfehler
#pragma once
, damit der Compiler keinen Header enthalten kann, wenn er bereits in der aktuellen .cpp-Datei enthalten ist, die kompiliert wirdIch hatte kürzlich dieses Problem und es stellte sich heraus, dass es war ein Fehler in Visual Studio Express 2013 Ich musste eine Quelldatei aus dem Projekt entfernen und sie erneut hinzufügen, um den Fehler zu beheben.
Schritte, um zu versuchen, ob 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; In der Regel fügen Sie der Befehlszeile -v -Wl,--verbose
oder -v -Wl,-v
hinzu. Weitere Details finden Sie hier.
Für MSVC wird /VERBOSE
(insbesondere /VERBOSE:LIB
) zur Verbindungsbefehlszeile hinzugefügt.
/VERBOSE
.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, während die DLL für MyProject erstellt wurde. Ich habe auch keinen Quellcode für alle Methoden im MyProject enthalten, sondern nur Zugriff auf die Einstiegspunkte der DLL.
Um das Problem zu lösen, habe ich MyProject als LIB erstellt und TestProject mit dieser LIB-Datei verknüpft (ich kopiere die generierte LIB-Datei in den Ordner TestProject). Ich kann dann MyProject wieder als DLL aufbauen. 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 Linker-Fehlern auf diese Frage verwiesen zu sein scheinen, werde ich dies hier hinzufügen.
Ein möglicher Grund für Linker-Fehler bei GCC 5.2.0 ist, dass jetzt standardmäßig eine neue libstdc ++ - Bibliotheks-ABI ausgewählt wird.
Wenn Sie Linker-Fehler bei undefinierten Verweisen auf Symbole erhalten, die Typen im Namespace std :: __ cxx11 oder das Tag [abi: cxx11] enthalten, weist dies wahrscheinlich darauf hin, dass Sie versuchen, Objektdateien miteinander zu verknüpfen, die mit unterschiedlichen Werten für _GLIBCXX_USE_CXX11_ABI kompiliert wurden Makro. Dies tritt häufig auf, wenn auf eine Bibliothek eines Drittanbieters verlinkt wird, die mit einer älteren Version von GCC kompiliert wurde. Wenn die Drittanbieter-Bibliothek nicht mit der neuen ABI neu erstellt werden kann, müssen Sie Ihren Code mit der alten ABI neu kompilieren.
Wenn Sie nach 5.1.0 plötzlich auf einen Link zu einem GCC umsteigen, ist dies eine Sache, die Sie überprüfen sollten.
Geben Sie das Code-Snippet eines Vorlagentyps mit einem Freundoperator (oder einer Funktion) an.
template <typename T>
class Foo {
friend std::ostream& operator<< (std::ostream& os, const Foo<T>& a);
};
Das operator<<
Wird als Nicht-Template-Funktion deklariert. Für jeden Typ T
, der mit Foo
verwendet wird, muss ein operator<<
Ohne Vorlage vorhanden sein. 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 Typ Foo
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 Instanz von Foo
, d. H. Die Instanz operator<< <int>
Ist auf den Zugriff auf die privaten Mitglieder der Instanz von Foo<int>
Beschränkt.
Alternativen umfassen;
Ermöglichen, dass sich die Freundschaft auf alle Instantiierungen der Vorlagen erstreckt, wie folgt;
template <typename T>
class Foo {
template <typename T1>
friend std::ostream& operator<<(std::ostream& os, const Foo<T1>& a);
// ...
};
Die Implementierung für operator<<
Kann auch direkt in der Klassendefinition erfolgen.
template <typename T>
class Foo {
friend std::ostream& operator<<(std::ostream& os, const Foo& a)
{ /*...*/ }
// ...
};
Hinweis : Wenn die Deklaration des Operators (oder der Funktion) nur in der Klasse angezeigt wird, steht der Name nicht für die "normale" Suche zur Verfügung, sondern nur für Argumente abhängige Suche von cppreference ;
Ein Name, der zuerst in einer Friend-Deklaration innerhalb der Klasse oder Klassenvorlage X deklariert wurde, wird Mitglied des innersten einschließenden Namespaces von X, kann jedoch nicht nachgeschlagen werden (mit Ausnahme der argumentabhängigen Suche unter Berücksichtigung von X), es sei denn, es liegt eine übereinstimmende Deklaration im Namespace-Bereich vor unter der Voraussetzung...
Weitere Informationen zu Vorlagenfreunden finden Sie unter cppreference und C++ FAQ .
Codeliste mit den obigen 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)
Ein Wrapper um GNU ld, der keine Linker-Skripts unterstützt
Einige .so-Dateien sind tatsächlich GNU ld-Linkerskripte , z. libtbb.so Die Datei ist eine Textdatei mit dem Inhalt ASCII:
INPUT (libtbb.so.2)
Einige komplexere Builds unterstützen dies möglicherweise nicht. Wenn Sie -v in die Compileroptionen einschließen, können Sie beispielsweise sehen, dass die mainwin gcc wrapper mwdip discards Skriptbefehlsdateien in der ausführlichen Ausgabeliste der zu verknüpfenden Bibliotheken enthalten Linker-Skript-Eingabebefehlsdatei mit einer Kopie der Datei (oder eines Symlinks), z.
cp libtbb.so.2 libtbb.so
Oder Sie können das Argument -l durch den vollständigen Pfad von .so ersetzen, z. anstelle von -ltbb
do /home/foo/tbb-4.3/linux/lib/intel64/gcc4.4/libtbb.so.2
libfoo
von libbar
abhängt, setzt Ihre Verknüpfung libfoo
korrekt vor libbar
.undefined reference to
etwas Fehlern fehl.#include
D haben, und tatsächlich 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 mit libmy_lib.a
Zu verknüpfen und schlagen fehl:
$ 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 libz
zu verknüpfen, 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 in einem Zug kompilieren und verlinken:
$ 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 den Objektdateien platzieren, die auf sie verweisen.
Beispiel 1 richtig verlinken:
$ gcc -o eg1 eg1.o -L. -lmy_lib
Erfolg:
$ ./eg1
Hello World
Verknüpfe Beispiel 2 richtig:
$ gcc -o eg2 eg2.o -lz
Erfolg:
$ ./eg2
1.2.8
Verknüpfen Sie die Variation von Beispiel 2 pkg-config
Richtig:
$ gcc -o eg2 eg2.o $(pkg-config --libs zlib)
$ ./eg2
1.2.8
Lesen ist ab hier optional .
Standardmäßig verbraucht ein von GCC auf Ihrer Distribution generierter Verknüpfungsbefehl die Dateien in der Verknüpfung von links nach rechts in Befehlszeilenreihenfolge. Wenn festgestellt wird, dass eine Datei auf irgendetwas verweist und keine Definition dafür enthält, wird in Dateien weiter rechts nach einer Definition gesucht. Wenn schließlich eine Definition gefunden wird, wird die Referenz aufgelöst. Wenn am Ende Referenzen ungelöst bleiben, schlägt die Verknüpfung fehl: Der Linker sucht nicht rückwärts.
Zuerst Beispiel 1 mit statischer Bibliothek my_lib.a
Eine statische Bibliothek ist ein indiziertes Archiv von Objektdateien. Wenn der Linker in der Verknüpfungssequenz -lmy_lib
Findet und feststellt, dass dies auf die statische Bibliothek ./libmy_lib.a
Verweist, möchte er wissen, ob Ihr Programm eine der Objektdateien in libmy_lib.a
.
Es gibt nur eine Objektdatei in libmy_lib.a
, Nämlich my_lib.o
, Und in my_lib.o
Ist nur eine Sache definiert, nämlich die Funktion hw
.
Der Linker entscheidet, dass Ihr Programm genau dann my_lib.o
Benötigt, wenn er bereits weiß, dass Ihr Programm auf hw
verweist, und zwar in einer oder mehreren der Objektdateien, die es dem Programm bereits hinzugefügt hat dass keine der bereits hinzugefügten Objektdateien eine Definition für hw
enthält.
Wenn dies zutrifft, extrahiert der Linker eine Kopie von my_lib.o
Aus der Bibliothek und fügt sie Ihrem Programm hinzu. Dann enthält Ihr Programm eine Definition für hw
, sodass die Verweise auf hw
resolved lauten.
Wenn Sie versuchen, das Programm wie folgt zu verknüpfen:
$ gcc -o eg1 -L. -lmy_lib eg1.o
der Linker hat nicht hinzugefügt eg1.o
zum Programm , wenn -lmy_lib
angezeigt wird. Weil es zu diesem Zeitpunkt noch nicht eg1.o
Gesehen hat. Ihr Programm enthält noch keine Verweise auf hw
: Es enthält noch keine Verweise überhaupt , da sich alle darin enthaltenen Verweise in eg1.o
.
Der Linker fügt dem Programm also nicht my_lib.o
Hinzu und hat keine weitere Verwendung für libmy_lib.a
.
Als nächstes findet es eg1.o
Und fügt es als Programm hinzu. Eine Objektdatei in der Verknüpfungssequenz wird dem Programm immer hinzugefügt. Das Programm verweist nun auf hw
und enthält keine Definition von hw
. Die Verknüpfungssequenz enthält jedoch nichts mehr, was die fehlende Definition liefern könnte. Der Verweis auf hw
endet mit unresolved , und die Verknüpfung schlägt fehl.
Zweitens Beispiel 2 mit gemeinsam genutzter Bibliothek libz
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 von ihm definierte Symbole verfügbar macht, damit andere Programme sie zur Laufzeit verwenden können .
Viele Linux-Distributionen konfigurieren heute ihre GCC-Toolchain so, dass ihre Sprachtreiber (gcc
, g++
, gfortran
usw.) den Systemlinker (ld
) anweisen, eine Verknüpfung herzustellen Shared Libraries auf einer as-needed Basis. Sie haben eine dieser Distributionen.
Dies bedeutet, dass der Linker, wenn er in der Verknüpfungssequenz -lz
Findet und feststellt, dass dies auf die gemeinsam genutzte Bibliothek (etwa) /usr/lib/x86_64-linux-gnu/libz.so
Verweist, wissen möchte, ob Referenzen hinzugefügt wurden Ihr noch nicht definiertes Programm enthält Definitionen, die von libz
exportiert werden.
Wenn dies zutrifft, kopiert der Linker not alle Chunks aus libz
und fügt sie Ihrem Programm hinzu. Stattdessen wird nur der Code Ihres Programms überprüft, sodass:
Zur Laufzeit lädt das Systemprogramm-Ladeprogramm eine Kopie von libz
in denselben Prozess wie Ihr Programm, wenn es eine Kopie Ihres Programms lädt, um es auszuführen.
Wenn Ihr Programm zur Laufzeit auf etwas verweist, das in libz
definiert ist, verwendet diese Referenz die Definition, die von der Kopie von libz
in demselben Prozess exportiert wurde.
Ihr Programm möchte nur auf eine Sache verweisen, deren Definition von libz
exportiert wurde, nämlich die Funktion zlibVersion
, auf die in eg2.c
Nur einmal verwiesen wird. Wenn der Linker diesen Verweis zu Ihrem Programm hinzufügt und dann die von libz
exportierte Definition findet, lautet der Verweis resolved
Aber wenn Sie versuchen, das Programm wie folgt zu verknüpfen:
gcc -o eg2 -lz eg2.o
die Reihenfolge der Ereignisse ist genauso falsch wie in Beispiel 1. An dem Punkt, an dem der Linker -lz
findet, gibt es no Verweise auf irgendetwas in der Programm: Sie sind alle in eg2.o
, was noch nicht gesehen wurde. Der Linker entscheidet also, dass es keine Verwendung für libz
gibt. Wenn es eg2.o
Erreicht, es dem Programm hinzufügt und dann einen undefinierten Verweis auf zlibVersion
hat, ist die Verknüpfungssequenz beendet; Diese Referenz ist nicht aufgelöst und die Verknüpfung schlägt fehl.
Schließlich hat die pkg-config
- Variante von Beispiel 2 eine jetzt offensichtliche Erklärung. Nach der Shell-Erweiterung:
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 Sie!
(Oder: Diese Verknüpfung hat zum Beispiel auf Fedora 23 einwandfrei funktioniert, schlägt aber auf Ubuntu 16.04 fehl.)
Dies liegt daran, dass die Distribution, auf der die Verknüpfung funktioniert, eine Distribution ist, die ihre GCC-Toolchain nicht so konfiguriert, dass sie gemeinsam genutzte Bibliotheken verknüpft nach Bedarf .
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 auf der in Beispiel 1 erläuterten Basis as-needed verknüpft, gemeinsam genutzte Bibliotheken wurden jedoch bedingungslos verknüpft.
Dieses Verhalten ist während der Laufzeit wirtschaftlich, da der Linker nicht darüber nachdenken muss, ob eine gemeinsam genutzte Bibliothek vom Programm benötigt wird. 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:
Dies ist bei - Laufzeit unwirtschaftlich, da dadurch gemeinsam mit einem Programm gemeinsam genutzte Bibliotheken geladen werden können, 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 -lfoo
In ihrer Verknüpfung zu /some/where/libfoo.a
Oder zu /some/where/libfoo.so
Aufgelöst wird. und möglicherweise nicht den Unterschied zwischen gemeinsam genutzten und statischen Bibliotheken sowieso verstehen.
Dieser Kompromiss hat heute zu einer schismatischen Situation geführt. Einige Distributionen haben ihre GCC-Verknüpfungsregeln für gemeinsam genutzte Bibliotheken geändert, sodass das Prinzip nach Bedarf für alle Bibliotheken gilt. Einige Distributionen haben sich an den alten Weg gehalten.
Wenn ich nur mache:
$ gcc -o eg1 -I. -L. -lmy_lib eg1.c
sicherlich muss gcc zuerst eg1.c
kompilieren und dann die resultierende Objektdatei mit libmy_lib.a
verknüpfen. Wie kann es also nicht wissen, dass eine Objektdatei benötigt wird, wenn die Verknüpfung ausgeführt wird?
Weil das Kompilieren und Verknüpfen mit einem einzigen Befehl die Reihenfolge der Verknüpfungssequenz nicht ändert.
Wenn Sie den obigen Befehl ausführen, stellt gcc
fest, dass Sie Kompilierung + Verknüpfung möchten. Hinter den Kulissen wird also ein Kompilierungsbefehl generiert und ausgeführt. Anschließend wird ein Verknüpfungsbefehl generiert und ausgeführt, als ob you die beiden Befehle ausgeführt hätte:
$ gcc -I. -c -o eg1.o eg1.c
$ gcc -o eg1 -L. -lmy_lib eg1.o
Die Verknüpfung schlägt also genauso fehl wie wenn Sie do diese beiden Befehle ausführen. Der einzige Unterschied, den Sie an dem Fehler bemerken, besteht darin, dass gcc eine temporäre Objektdatei in der Groß- und Kleinschreibung compile + link generiert hat, weil Sie nicht anweisen, eg1.o
Zu verwenden. Wir sehen:
/tmp/ccQk1tvs.o: In function `main'
anstatt von:
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, die ) Definitionen von Dingen benötigen, die später in der Verknüpfung auftreten als die Dateien, die ) bereitstellen die Definitionen. Das Einfügen von Bibliotheken vor die darauf verweisenden Objektdateien ist eine weitere Möglichkeit, denselben Fehler zu begehen.
UNICODE
DefinitionenEin Windows-UNICODE-Build wird erstellt, wobei TCHAR
usw. als wchar_t
Usw. definiert ist. Wenn kein Build mit UNICODE
als Build mit TCHAR
definiert ist als char
usw. Diese UNICODE
- und _UNICODE
- Definitionen wirken sich auf alle "T
" - Zeichenfolgentypen ; LPTSTR
, LPCTSTR
und ihre Elche.
Wenn Sie eine Bibliothek mit UNICODE
definieren und versuchen, sie in einem Projekt zu verknüpfen, in dem UNICODE
nicht definiert ist, führt dies zu Linkerfehlern, da die Definition von TCHAR
; char
vs. wchar_t
.
Der Fehler enthält normalerweise eine Funktion, die einen Wert mit einem von char
oder wchar_t
Abgeleiteten Typ enthält. 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 für beide UNICODE bestimmt war und ein Multi-Byte-Zeichen (oder "eng") bauen.
Um dies zu korrigieren, erstellen Sie alle erforderlichen Bibliotheken und Projekte mit einer konsistenten Definition von UNICODE
(und _UNICODE
).
Dies kann entweder mit getan werden;
#define UNICODE
#define _UNICODE
Oder in den Projekteinstellungen;
Projekteigenschaften> Allgemein> Projektstandards> Zeichensatz
Oder auf der Kommandozeile;
/DUNICODE /D_UNICODE
Die Alternative gilt auch, 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.
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 gleicht eine Funktionsdeklaration (in der Kopfzeile deklariert) mit seiner Definition (in der gemeinsam genutzten Bibliothek) ab, indem er deren Signaturen vergleicht. Sie können einen Linker-Fehler erhalten, wenn der Linker keine perfekt passende Funktionsdefinition findet.
Ist es möglich, immer noch einen Linker-Fehler zu erhalten, obwohl die Deklaration und die Definition zu stimmen scheinen? Ja! Sie sehen im Quellcode möglicherweise gleich aus, aber es hängt wirklich davon ab, was der Compiler sieht. Im Wesentlichen könnte es zu einer Situation wie dieser kommen:
// header1.h
typedef int Number;
void foo(Number);
// header2.h
typedef float Number;
void foo(Number); // this only looks the same lexically
Beachten Sie, wie beide Funktionsdeklarationen zwar im Quellcode identisch aussehen, sich jedoch je nach Compiler unterscheiden.
Sie fragen sich vielleicht, wie man in so einer Situation landet? Pfade einschließen natürlich! Wenn der Include-Pfad beim Kompilieren der gemeinsam genutzten Bibliothek zu header1.h
führt und Sie header2.h
in Ihrem eigenen Programm verwenden, bleibt Ihnen der Kopf zerkratzen und Sie fragen sich, was passiert ist.
Ein Beispiel, wie dies in der realen Welt passieren kann, wird unten erläutert.
Ich habe zwei Projekte: graphics.lib
und main.exe
. Beide Projekte sind abhängig 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
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 Sie haben keine Ahnung, warum der Fehler auftritt. Der Grund ist, dass in der allgemeinen Bibliothek verschiedene Versionen desselben Include-Codes common_math.h
verwendet werden (ich habe es hier im Beispiel durch Einfügen eines anderen Pfads deutlich gemacht, aber es ist möglicherweise nicht immer so offensichtlich. Vielleicht ist der Include-Pfad in den Compilereinstellungen unterschiedlich ).
Beachten Sie in diesem Beispiel, dass der Linker Ihnen sagen würde, dass er draw()
nicht finden konnte, wenn Sie tatsächlich wissen, dass er offensichtlich von der Bibliothek exportiert wird. Sie könnten stundenlang am Kopf kratzen und sich fragen, was schief gelaufen ist. Die Sache ist, der Linker sieht eine andere Signatur, weil die Parametertypen etwas anders sind. Im Beispiel ist vec3
in beiden Projekten ein anderer Typ, was den Compiler betrifft. Dies kann passieren, weil sie aus zwei etwas 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 andere ähnliche Tools haben.
Der Prozess läuft so ab:
[1] Unter Projekt meine ich eine Menge von Quelldateien, die miteinander verbunden sind, um entweder eine Bibliothek oder eine ausführbare Datei zu erzeugen.
EDIT 1: Der erste Abschnitt wurde umgeschrieben, um ihn verständlicher zu machen. Bitte kommentieren Sie unten, um mich zu informieren, wenn etwas anderes behoben werden muss. Vielen Dank!
Ein "Clean" des Builds kann das "tote Holz" entfernen, das von vorherigen Builds, fehlgeschlagenen Builds, unvollständigen Builds und anderen Build-System-Problemen übrig bleiben kann.
Im Allgemeinen enthält das IDE oder build eine Form der "clean" -Funktion, diese ist jedoch möglicherweise nicht richtig konfiguriert (z. B. in einem manuellen Makefile) oder kann fehlschlagen (z. B. sind die intermediären oder resultierenden Binaries schreibgeschützt ).
Nachdem die "Bereinigung" abgeschlossen ist, vergewissern Sie sich, dass 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 in Zusammenhang stehende 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, kann es eine Überraschung sein, dass const
variablen in C++ interne (oder statische) Verknüpfungen haben. In C war dies nicht der Fall, da alle globalen Variablen implizit extern
sind (d. H. Wenn das Schlüsselwort static
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, eine Header-Datei zu verwenden und sie in file2.cpp und file1.cpp einzubinden
extern const int test;
extern int test2;
Alternativ könnte man die Variable const
in file1.cpp mit expliziter extern
deklarieren.
Auch wenn dies eine ziemlich alte Frage mit mehreren akzeptierten Antworten ist, möchte ich mitteilen, wie ein obskurer "undefinierter Verweis auf" Fehler behoben werden kann.
Ich habe einen Alias verwendet, um auf std::filesystem::path
Zu verweisen: Dateisystem ist seit C++ 17 in der Standardbibliothek, aber mein Programm musste auch in C++ 14 kompilieren, also habe ich mich für die Verwendung entschieden ein variabler Alias:
#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 das verschiedene Bibliotheken, das in main.cpp und file.h verwendet wird. Da main.cpp # include'd "file.h" nach <filesystem> ist, war die Version des dort verwendeten 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 nach 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
-> verbindet main.o und file.o
Auf diese Weise jede Funktion in file.o enthalten und in main.o verwendet, dass erforderlich path_t
"undefinierte Referenz" Fehler lieferte, weil main. o bezieht sich auf std::filesystem::path
aber file.o auf std::experimental::filesystem::path
.
Um dies zu beheben, musste ich nur <experimental :: filesystem> in file.h in <filesystem> ändern.
Das Standardverhalten von gcc ist, dass alle Symbole sichtbar sind. Wenn die Übersetzungseinheiten jedoch mit der Option -fvisibility=hidden
erstellt werden, sind nur die mit __attribute__ ((visibility ("default")))
gekennzeichneten Funktionen/Symbole im resultierenden Shared Object extern.
Sie können überprüfen, ob die Symbole, nach denen Sie suchen, 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 durch nm
mit Kleinbuchstaben angezeigt, zum Beispiel 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 -C
verwenden, um die Namen zu entwirren (falls C++ verwendet wurde).
Ähnlich wie bei Windows-DLLs würde man öffentliche Funktionen mit einem Definieren kennzeichnen, 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
kompiliert wird, haben die resultierenden Symbole immer noch eine externe Verknüpfung (durch nm
mit Großbuchstaben dargestellt) und können problemlos für eine externe Verknüpfung verwendet werden, wenn die Objektdateien Teil einer statischen Bibliothek werden. Die Verknüpfung wird nur dann lokal, wenn die Objektdateien in eine gemeinsam genutzte Bibliothek eingebunden sind.
So finden Sie heraus, welche Symbole in einer Objektdatei verborgen sind:
>>> objdump -t XXXX.o | grep hidden
0000000000000000 g F .text 000000000000000b .hidden HIDDEN_SYMBOL1
000000000000000b g F .text 000000000000000b .hidden HIDDEN_SYMBOL2
Verschiedene 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 als die, für die Sie kompilieren, sind.
In Visual Studio ist dies auf die falsche "Plattform" zurückzuführen, und Sie müssen entweder die richtige auswählen oder die richtige Version der Bibliothek installieren.
Unter Linux kann es an einem falschen Bibliotheksordner liegen (beispielsweise 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 es gibt nur eine. Es kann auch ein Problem mit dem falschen lib
/lib64
-Ordner sein, in dem die Bibliothek abgerufen wird.
Der Fehler tritt auf, wenn der Linker keine Definition für eine Definition in der Deklarationsdatei finden konnte, d. H. Von einer Header- oder Deklarationsdatei. Es tritt auf, wenn die Implementierung nicht gefunden wurde.
Nach der Kompilierung versucht ein Linker, die Implementierungen aus einer Bibliothek oder einer virtuellen Funktion oder einer Vorlagendefinition zu finden. Wenn es keinen findet, treten diese Fehler gemäß der Linker-Architektur auf.
Ubuntu hat ein Debian-gepatchtes gcc-4.4/ g ++ - 4.4 : Nicht gut mit diesem Code (und einigen anderen ns2-Codes/Patches auch.)
Ubuntu: mpolsr_umolsr-v1_ns235.patch
→ https://drive.google.com/file/d/0B7S...ew?usp=sharing (erstellt im Jahr 2017 mit dem mpolsr-Code, keine Änderungen.)
tar xvf ns-allinone-2.35_gcc5.tar.gz
https://drive.google.com/file/d/0B7S...ew?usp=sharing
cd ns-allinone-2.35/
patch -p0 < mpolsr_umolsr-v1_ns235.patch // umolsr version v1.0 is used
./install
// Stops with MPOLSR.cc
cd ns-2.35/
// Edit the Makefile line 37 to CPP = g++34 , and run make
make
// When 'make' stops with an mdart/* error, change to CPP = g++-4.4
make
gcc34 Ubuntu https://drive.google.com/file/d/0B7S255p3kFXNRTkzQnRSNXZ6UVU/view?usp=sharing
g ++ 34 Ubuntu https://drive.google.com/file/d/0B7S255p3kFXNV3J3bnVoWGNWdG8/view?usp=sharing
inline
definiert.Ein Beispiel:-
main.cpp
#include "gum.h"
#include "foo.h"
int main()
{
gum();
foo f;
f.bar();
return 0;
}
foo.h (1)
#pragma once
struct foo {
void bar() const;
};
gum.h (1)
#pragma once
extern void gum();
foo.cpp (1)
#include "foo.h"
#include <iostream>
inline /* <- wrong! */ void foo::bar() const {
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
gum.cpp (1)
#include "gum.h"
#include <iostream>
inline /* <- wrong! */ void gum()
{
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
Wenn Sie angeben, dass gum
(in ähnlicher Weise foo::bar
) bei seiner Definition inline
ist, dann Wird der Compiler gum
(falls er dies wählt) inline eingebunden, indem Sie: -
gum
ausgeben und dahergum
verweisen kann, und stattdessengum
durch Inline-Kopien des kompilierten Hauptteils von gum
.Wenn Sie also gum
inline in einer Quelldatei gum.cpp
definieren, wird __ zu einer Objektdatei gum.o
kompiliert, in der alle Aufrufe von gum
Inline Sind und kein Symbol definiert ist, durch das der Linker gum
referenzieren kann. . Wenn Sielink gum.o
zusammen mit einer anderen Objektdatei, z. main.o
Verweise auf ein externes Symbol gum
, der Linker kann diese Verweise nicht auflösen. Die Verbindung schlägt also fehl:
Kompilieren:
g++ -c main.cpp foo.cpp gum.cpp
Verknüpfung:
$ g++ -o prog main.o foo.o gum.o
main.o: In function `main':
main.cpp:(.text+0x18): undefined reference to `gum()'
main.cpp:(.text+0x24): undefined reference to `foo::bar() const'
collect2: error: ld returned 1 exit status
Sie können gum
nur als inline
definieren, wenn der Compiler seine Definition in jeder Quelldatei sehen kann, in der gum
aufgerufen werden kann. Das heißt, seine Inline-Definition muss in einer header -Datei vorhanden sein, die Sie include in jeder Quelldatei enthalten Sie kompilieren, in der gum
aufgerufen werden kann. Machen Sie eine von zwei Sachen:
Entweder Inline-Definitionen nicht
Entfernen Sie den inline
-Bezeichner aus der Quellendatendefinition:
foo.cpp (2)
#include "foo.h"
#include <iostream>
void foo::bar() const {
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
gum.cpp (2)
#include "gum.h"
#include <iostream>
void gum()
{
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
Wiederaufbau mit dem:
$ g++ -c main.cpp foo.cpp gum.cpp
[email protected]:~/develop/so/scrap1$ g++ -o prog main.o foo.o gum.o
[email protected]:~/develop/so/scrap1$ ./prog
void gum()
void foo::bar() const
Erfolg.
Oder richtig inline
Inline-Definitionen in Header-Dateien:
foo.h (2)
#pragma once
#include <iostream>
struct foo {
void bar() const { // In-class definition is implicitly inline
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
};
// Alternatively...
#if 0
struct foo {
void bar() const;
};
inline void foo::bar() const {
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
#endif
gum.h (2)
#pragma once
#include <iostream>
inline void gum() {
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
Nun brauchen wir nicht foo.cpp
oder gum.cpp
:
$ g++ -c main.cpp
$ g++ -o prog main.o
$ ./prog
void gum()
void foo::bar() const