it-swarm.com.de

Was ist ein nicht definierter Verweis/nicht aufgelöster Fehler eines externen Symbols und wie kann ich diesen beheben?

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.

1309
Luchian Grigore

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

  1. Physische Quelldateizeichen werden implementierungsdefiniert dem grundlegenden Quellzeichensatz .__ zugeordnet. (Einführung neuer Zeilenzeichen für Zeilenendeindikatoren) if notwendig. [SNIP]
  2. 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]
  3. Die Quelldatei wird in Vorverarbeitungstoken (2.5) und Sequenzen von Leerzeichen (einschließlich Kommentaren) zerlegt. [SNIP]
  4. Vorbearbeitungsdirektiven werden ausgeführt, Makroaufrufe werden erweitert und unäre Operatorausdrücke _Pragma werden ausgeführt. [SNIP]
  5. 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]
  6. Angrenzende String-Literal-Token werden verkettet.
  7. 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]
  8. Übersetzte Übersetzungseinheiten und Instanziierungseinheiten werden wie folgt kombiniert: [SNIP]
  9. 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.cppdeklariert 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 tosymbolName 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:

761
Luchian Grigore

Klassenmitglieder:

Ein reiner virtual-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;
};

Nicht -virtual Klassenmitglieder

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

struct A
{ 
    ~A();
};

Folgendes würde den Fehler ergeben:

A a;      //destructor undefined

Die Implementierung kann in der Klassendefinition selbst 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.

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

struct A
{
   void foo();
};

void foo() {}

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

Die Definition sollte sein

void A::foo() {}

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 staticconst-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.

163
Luchian Grigore

Fehler beim Verbinden mit entsprechenden Bibliotheken/Objektdateien oder Kompilieren von Implementierungsdateien

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".

102
Luchian Grigore

Deklariert, aber keine Variable oder Funktion definiert.

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 einem Namespace deklarierte Funktion/Variable, definiert in einem anderen Namespace.
  • Als Klassenmitglied deklarierte Funktion/Variable, definiert als global (oder umgekehrt).
  • Funktionsrückgabetyp, Parameternummer und -typen und Aufrufkonvention stimmen nicht alle genau überein.

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.

94
Luchian Grigore

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

Die Reihenfolge, in der Bibliotheken verknüpft sind, spielt eine Rolle, wenn die Bibliotheken voneinander abhängig sind. Wenn die Bibliothek A im Allgemeinen von der Bibliothek B abhängt, muss libAvor 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!

77
Svalorzen

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.

68
Kastaneda

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

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

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

Der C++ - Linker erwartet, dass Namen 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"
}
64
Luchian Grigore

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.

61
sgryzko

Falscher Import/Export von Methoden/Klassen über Module/DLL (compilerspezifisch).

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
{
};
52
Luchian Grigore

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:

  • Alle Objektdateien, die während der Kompilierungszeit generiert wurden
  • Alle Bibliotheken (.lib), die entweder explizit oder implizit Als zusätzliche Abhängigkeiten dieser Gebäudeanwendung angegeben sind.

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.

  1. Definition existiert

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.

  1. Definition existiert nicht

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.

50
Nima Soroush

Vorlagenimplementierungen nicht sichtbar.

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 .

50
Luchian Grigore

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.

37

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

34
Dula

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.

33
Niall

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.

31
Malvineous

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?

  • Visual Studio durchsucht die Datei .vcxproj und beginnt mit dem Kompilieren der Dateien mit der Erweiterung .cpp. Die Reihenfolge der Kompilierung ist undefined.So dürfen Sie nicht davon ausgehen, dass die Datei main.cpp zuerst kompiliert wird
  • Wenn .cpp-Dateien auf zusätzliche .h-Dateien angewiesen sind, um nach Symbolen zu suchen, die in der Datei .cpp definiert sind oder nicht
  • Wenn eine .cpp-Datei vorhanden ist, in der der Compiler kein Symbol finden konnte, löst ein Compiler-Zeitfehler die Nachricht aus. Symbol x konnte nicht gefunden werden.
  • Für jede Datei mit der Erweiterung .cpp wird eine Objektdatei .o generiert, und auch Visual Studio schreibt die Ausgabe in eine Datei mit dem Namen ProjectName.Cpp.Clean.txt, die alle Objektdateien enthält, die vom Linker verarbeitet werden müssen.

Der zweite Schritt der Kompilierung wird von Linker ausgeführt. 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 

  • Durchsuchen Sie alle Objektdateien und suchen Sie die Definition, die nur in Headern deklariert wurde (z. B .: Code einer Klasse einer Klasse, wie in den vorherigen Antworten erwähnt, oder Ereignis die Initialisierung einer statischen Variablen, die Mitglied einer Klasse ist).
  • Wenn ein Symbol nicht in Objektdateien gefunden werden konnte, wird er auch in Zusätzliche Bibliotheken gesucht. Um eine neue Bibliothek zu einem Projekt hinzuzufügen, Konfigurationseigenschaften -> VC++ - Verzeichnisse -> Bibliothek Verzeichnisse und hier haben Sie einen zusätzlichen Ordner zum Durchsuchen von Bibliotheken und Konfigurationseigenschaften -> Linker -> Eingabe zur Angabe des Bibliotheksnamens angegeben. - Wenn der Linker das von Ihnen geschriebene Symbol nicht in einer CPP-Datei finden konnte, gibt er einen Linker-Zeitfehler aus, der sich wie error LNK2001: unresolved external symbol "void __cdecl foo(void)" ([email protected]@YAXXZ) anhören kann.

Überwachung

  1. Sobald der Linker ein Symbol gefunden hat, sucht er nicht in anderen Bibliotheken danach
  2. Die Reihenfolge der Verknüpfungsbibliotheken ist wichtig.
  3. Wenn Linker ein externes Symbol in einer statischen Bibliothek findet, fügt er das Symbol in die Ausgabe des Projekts ein. Wenn die Bibliothek jedoch gemeinsam genutzt wird (dynamisch), nimmt sie nicht den Code (die Symbole) in die Ausgabe auf, sondern Laufzeit Abstürze können auftreten

So lösen Sie diese Art von Fehler

Compiler-Zeitfehler: 

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

Linker-Zeitfehler

  • Definieren Sie alle Ihre Symbole, die Sie in Ihren Header-Dateien deklarieren
  • Verwenden Sie #pragma once, damit der Compiler keinen Header enthalten kann, wenn er bereits in der aktuellen .cpp-Datei enthalten ist, die kompiliert wird
  • Stellen Sie sicher, dass Ihre externe Bibliothek keine Symbole enthält, die in Konflikt mit anderen Symbolen treten können, die Sie in Ihren Header-Dateien definiert haben
  • Wenn Sie die Vorlage verwenden, um sicherzustellen, dass Sie die Definition jeder Vorlagenfunktion in die Headerdatei aufnehmen, damit der Compiler den entsprechenden Code für alle Instantiierungen generieren kann. 
30
user4272649

Ein Fehler im Compiler/IDE

Ich 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:

  • Bereinigen Sie das Projekt (einige IDEs haben eine Option, dies kann auch manuell erledigt werden, indem Sie die Objektdateien löschen.)
  • Versuchen Sie, ein neues Projekt zu starten Kopieren Sie den gesamten Quellcode vom ursprünglichen.
26
developerbmw

Verwenden Sie den Linker, um den Fehler zu diagnostizieren

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

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

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.

  • Die MSDN-Seite der Linker-Option /VERBOSE .
23
Niall

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. 

23
octoback

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.

18
Plankalkül

Vorlagen anfreunden ...

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)

18
Niall

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

17
JDiMatteo

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

  • Sie versuchen, Ihr Programm mit der GCC-Toolchain zu kompilieren und zu verknüpfen.
  • Ihre Verknüpfung gibt alle erforderlichen Bibliotheken und Bibliothekssuchpfade an
  • Wenn libfoo von libbar abhängt, setzt Ihre Verknüpfung libfoo korrekt vor libbar.
  • Ihre Verknüpfung schlägt mit undefined reference to etwas Fehlern fehl.
  • Aber alle undefinierten something s werden in den Header-Dateien deklariert, die Sie mit #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

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

my_lib.c

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

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

my_lib.h

#ifndef MY_LIB_H
#define MT_LIB_H

extern void hw(void);

#endif

eg1.c

#include <my_lib.h>

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

Sie erstellen Ihre statische Bibliothek:

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

Sie kompilieren Ihr Programm:

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

Sie versuchen, es 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

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

eg2.c

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

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

Kompilieren Sie Ihr Programm:

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

Versuchen Sie, Ihr Programm mit 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'

Was machst du falsch

In der Reihenfolge der Objektdateien und Bibliotheken, die Sie verknüpfen möchten, um Ihr Programm zu erstellen, platzieren Sie die Bibliotheken vor den Objektdateien, die auf sie verweisen. Sie müssen die Bibliotheken nach 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

Die Erklärung

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.

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

Die Verknüpfung:

gcc -o eg2 -lz eg2.o

funktioniert gut für 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.

Warum tritt dieses Problem immer noch auf, obwohl ich gleichzeitig kompiliere und verknüpfe?

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':

Siehe auch

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

Das Versetzen von voneinander abhängigen Bibliotheken in die falsche Reihenfolge ist nur eine Möglichkeit, Dateien abzurufen, 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.

17
Mike Kinghan

Inkonsistente UNICODE Definitionen

Ein 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).

  1. Dies kann entweder mit getan werden;

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

    Projekteigenschaften> Allgemein> Projektstandards> Zeichensatz

  3. 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.

15
Niall

Wenn Ihre Include-Pfade unterschiedlich sind

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

Wie funktionieren Linker? Der Linker 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.

Weitere Ausarbeitung mit einem Beispiel

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).

Den Linker debuggen

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. Beachten Sie den komischen Namen, der im Linker-Fehler angegeben wurde. (zB @grafik @ XYZ zeichnen).
  2. Speichern Sie die exportierten Symbole aus der Bibliothek in eine Textdatei.
  3. Suchen Sie nach dem exportierten Symbol von Interesse, und beachten Sie, dass der entstellte Name unterschiedlich ist.
  4. Achten Sie darauf, warum die verstümmelten Namen anders waren. Sie können feststellen, dass die Parametertypen unterschiedlich sind, obwohl sie im Quellcode gleich aussehen.
  5. Grund, warum sie anders sind. Im obigen Beispiel unterscheiden sie sich aufgrund unterschiedlicher Include-Dateien.

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

12
fafaro

Reinigen und neu aufbauen

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).

12
Niall

Fehlendes "extern" in const Variablendeklarationen/Definitionen (nur C++)

Für Leute, die aus C kommen, kann es eine Überraschung sein, dass constvariablen 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.

7
Andreas H.

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.

Verschiedene Versionen von Bibliotheken

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:

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

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.

Auflösung

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

6
Stypox

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

Das Standardverhalten von gcc ist, dass alle Symbole sichtbar sind. Wenn die Übersetzungseinheiten jedoch mit 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
2
ead

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.

0

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.

0
Karthik Krish
  • Der Code kann so kompiliert werden, wie er ist, mit g ++ 44 (Red Hat 4.4.7-8)

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.patchhttps://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

0
Knud Larsen

Funktionen oder Klassenmethoden werden in Quelldateien mit dem Bezeichner 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: -

  • keine eindeutige Definition von gum ausgeben und daher
  • kein Symbol ausgeben, durch das der Linker auf die Definition von gum verweisen kann, und stattdessen
  • ersetzen aller Aufrufe von gum 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
0
Mike Kinghan