Was sind undefinierte Referenz-/ungelöste externe Symbolfehler? Was sind häufige Ursachen und wie können sie behoben/verhindert werden?
Was ist ein undefinierter Verweis/ungelöster externer Symbolfehler und wie behebe ich ihn?
Luchian Grigore
Luchian Grigore
Klassenmitglieder:
Ein reines virtual
Destruktor benötigt eine Implementierung.
Um einen Destruktor als rein zu deklarieren, müssen Sie ihn immer 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, sodass eine Definition erforderlich ist.
virtual
Methoden müssen entweder implementiert oder als rein definiert werden.
Dies ist ähnlich wie bei Nicht-virtual
Methoden ohne Definition, mit der zusätzlichen Begründung, dass die reine Deklaration eine Dummy-vtable generiert und Sie möglicherweise den Linker-Fehler erhalten, ohne die Funktion zu verwenden:
struct X
{
virtual void foo();
};
struct Y : X
{
void foo() {}
};
int main()
{
Y y; //linker error although there was no call to X::foo
}
Damit dies funktioniert, deklarieren Sie X::foo()
als rein:
struct X
{
virtual void foo() = 0;
};
Nicht-virtual
Klassenmitglieder
Einige Mitglieder müssen definiert werden, auch wenn sie nicht explizit verwendet werden:
struct A
{
~A();
};
Folgendes würde den Fehler liefern:
A a; //destructor undefined
Die Implementierung kann inline in der Klassendefinition selbst erfolgen:
struct A
{
~A() {}
};
oder draußen:
A::~A() {}
Befindet sich die Implementierung außerhalb der Klassendefinition, aber in einem Header, müssen die Methoden als gekennzeichnet werden inline
um eine Mehrfachdefinition zu verhindern.
Alle verwendeten Elementmethoden 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
Datenmember müssen außerhalb der Klasse in a definiert werden einzelne Übersetzungseinheit:
struct X
{
static int x;
};
int main()
{
int x = X::x;
}
//int X::x; //uncomment this line to define X::x
Für a kann ein Initialisierer bereitgestellt werden static
const
Datenelement vom Typ Ganzzahl oder Aufzählung innerhalb der Klassendefinition; Die odr-Verwendung dieses Members erfordert jedoch weiterhin eine Namespace-Bereichsdefinition, wie oben beschrieben. C++11 ermöglicht die Initialisierung innerhalb der Klasse für alle static const
Daten Mitglieder.
-
Ich dachte nur, Sie möchten vielleicht betonen, dass beides möglich ist und der dtor keine Ausnahme darstellt. (Das geht aus deiner Formulierung nicht auf den ersten Blick hervor.)
– Deduplizierer
20. September 2014 um 19:26 Uhr
-
Es wäre gut, wenn Sie den häufigen Fehler von explizit abdecken könnten
gcc main.c
anstattgcc main.c other.c
(was Anfänger oft tun, bevor ihre Projekte so groß werden, dass sie .o-Dateien erstellen) .– MM
4. November 2019 um 20:43 Uhr
Luchian Grigore
Deklariert, aber keine Variable oder Funktion definiert.
Eine typische Variablendeklaration ist
extern int x;
Da dies nur eine Erklärung ist, a einzelne Definition wird gebraucht. Eine entsprechende Definition wäre:
int x;
Folgendes würde beispielsweise einen Fehler erzeugen:
extern int x;
int main()
{
x = 0;
}
//int x; // uncomment this line for successful definition
Ähnliche Bemerkungen gelten für Funktionen. Das Deklarieren einer Funktion ohne Definition führt zu folgendem Fehler:
void foo(); // declaration only
int main()
{
foo();
}
//void foo() {} //uncomment this line for successful definition
Achten Sie darauf, dass die von Ihnen implementierte Funktion genau mit der deklarierten übereinstimmt. Beispielsweise haben Sie möglicherweise nicht übereinstimmende CV-Qualifikatoren:
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 Nichtübereinstimmungen umfassen
- In einem Namensraum deklarierte Funktion/Variable in einem anderen definiert.
- Funktion/Variable als Klassenmitglied deklariert, als global definiert (oder umgekehrt).
- Funktionsrückgabetyp, Parameternummer und -typen sowie Aufrufkonventionen stimmen nicht alle genau überein.
Die Fehlermeldung des Compilers gibt Ihnen oft die vollständige Deklaration der Variablen oder Funktion, die deklariert, aber nie definiert wurde. Vergleichen Sie es genau mit der von Ihnen angegebenen Definition. Stellen Sie sicher, dass jedes Detail übereinstimmt.
-
In VS cpp-Dateien, die denen im Header entsprechen
#includes
nicht zum Quellverzeichnis hinzugefügt fällt ebenfalls unter die Kategorie der fehlenden Definitionen.– Laurie Stearn
30. März 2018 um 12:18 Uhr
Gemeinschaft
Die Reihenfolge, in der voneinander abhängige verknüpfte Bibliotheken angegeben werden, ist falsch.
Die Reihenfolge, in der Bibliotheken verknüpft sind, spielt eine Rolle, wenn die Bibliotheken voneinander abhängen. Im Allgemeinen, wenn Bibliothek A
hängt von der Bibliothek ab B
dann libA
MUSS davor erscheinen libB
in den Linker-Flags.
Zum Beispiel:
// B.h
#ifndef B_H
#define B_H
struct B {
B(int);
int x;
};
#endif
// B.cpp
#include "B.h"
B::B(int xx) : x(xx) {}
// A.h
#include "B.h"
struct A {
A(int x);
B b;
};
// A.cpp
#include "A.h"
A::A(int x) : b(x) {}
// main.cpp
#include "A.h"
int main() {
A a(5);
return 0;
};
Erstellen Sie die Bibliotheken:
$ g++ -c A.cpp
$ g++ -c B.cpp
$ ar rvs libA.a A.o
ar: creating libA.a
a - A.o
$ ar rvs libB.a B.o
ar: creating libB.a
a - B.o
Kompilieren:
$ g++ main.cpp -L. -lB -lA
./libA.a(A.o): In function `A::A(int)':
A.cpp:(.text+0x1c): undefined reference to `B::B(int)'
collect2: error: ld returned 1 exit status
$ g++ main.cpp -L. -lA -lB
$ ./a.out
Um die Reihenfolge noch einmal zu wiederholen TUT Gegenstand!
-
In VS cpp-Dateien, die denen im Header entsprechen
#includes
nicht zum Quellverzeichnis hinzugefügt fällt ebenfalls unter die Kategorie der fehlenden Definitionen.– Laurie Stearn
30. März 2018 um 12:18 Uhr
Was ist eine “undefinierte Referenz / nicht aufgelöstes externes Symbol”
Ich werde versuchen zu erklären, was eine “undefinierte Referenz / ein nicht aufgelöstes externes Symbol” ist.
Hinweis: Ich verwende g ++ und Linux und alle Beispiele sind dafür
Zum Beispiel haben wir einen Code
// src1.cpp
void print();
static int local_var_name; // 'static' makes variable not visible for other modules
int global_var_name = 123;
int main()
{
print();
return 0;
}
und
// src2.cpp
extern "C" int printf (const char*, ...);
extern int global_var_name;
//extern int local_var_name;
void print ()
{
// printf("%d%d\n", global_var_name, local_var_name);
printf("%d\n", global_var_name);
}
Erstellen Sie Objektdateien
$ 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. Betrachten Sie die Symbole
$ readelf --symbols src1.o
Num: Value Size Type Bind Vis Ndx Name
5: 0000000000000000 4 OBJECT LOCAL DEFAULT 4 _ZL14local_var_name # [1]
9: 0000000000000000 4 OBJECT GLOBAL DEFAULT 3 global_var_name # [2]
Ich habe einige Zeilen aus der Ausgabe verworfen, weil sie keine Rolle spielen
Wir sehen also folgende Symbole zum Exportieren.
[1] - this is our static (local) variable (important - Bind has a type "LOCAL")
[2] - this is our global variable
src2.cpp exportiert nichts und wir haben keine seiner Symbole gesehen
Verlinken Sie unsere Objektdateien
$ g++ src1.o src2.o -o prog
und führe es aus
$ ./prog
123
Linker sieht exportierte Symbole und verknüpft sie. Jetzt versuchen wir, Zeilen in src2.cpp wie hier zu entkommentieren
// 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 Objektdateien erstellen, ist die Verknüpfung noch nicht abgeschlossen. Versuche zu verlinken
$ g++ src1.o src2.o -o prog
src2.o: In function `print()':
src2.cpp:(.text+0x6): undefined reference to `local_var_name'
collect2: error: ld returned 1 exit status
Das ist passiert, weil unser local_var_name statisch ist, dh er ist für andere Module nicht sichtbar. Jetzt tiefer. Rufen Sie die Ausgabe der Übersetzungsphase ab
$ g++ -S src1.cpp -o src1.s
// src1.s
look src1.s
.file "src1.cpp"
.local _ZL14local_var_name
.comm _ZL14local_var_name,4,4
.globl global_var_name
.data
.align 4
.type global_var_name, @object
.size global_var_name, 4
global_var_name:
.long 123
.text
.globl main
.type main, @function
main:
; assembler code, not interesting for us
.LFE0:
.size main, .-main
.ident "GCC: (Ubuntu 4.8.2-19ubuntu1) 4.8.2"
.section .note.GNU-stack,"",@progbits
Wir haben also gesehen, dass es kein Label für local_var_name gibt, deshalb hat Linker es nicht gefunden. Aber wir sind Hacker 🙂 und wir können es reparieren. Öffnen Sie src1.s in Ihrem Texteditor und ändern Sie
.local _ZL14local_var_name
.comm _ZL14local_var_name,4,4
zu
.globl local_var_name
.data
.align 4
.type local_var_name, @object
.size local_var_name, 4
local_var_name:
.long 456789
dh 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 seinen Wert auf 456789 gesetzt. Versuchen Sie, daraus eine Objektdatei 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
jetzt hat local_var_name Bind GLOBAL (war LOCAL)
Verknüpfung
$ g++ src1.o src2.o -o prog
und führe es aus
$ ./prog
123456789
ok, wir hacken es 🙂
Infolgedessen tritt ein Fehler “undefinierter Verweis/nicht aufgelöstes externes Symbol” auf, wenn der Linker keine globalen Symbole in den Objektdateien finden kann.
@LuchianGrigore ‘Fühlen Sie sich frei, eine Antwort hinzuzufügen’ Ich habe es vorgezogen, den relevanten Link (IMHO) zu Ihrer primären Antwort hinzuzufügen, wenn Sie dies zulassen möchten.
– πάντα ῥεῖ
3. März 2014 um 22:36 Uhr
@jave.web: Während das passiert, bemerkt der Programmierer normalerweise, dass er keine hat
this
Zeiger und kein Zugriff auf Klassenmitglieder. Es kommt ziemlich selten vor, dass die Kompilierung abgeschlossen wird und nur während des Verknüpfens fehlschlägt, wenn einer nicht statischen Elementfunktion der qualifizierte Name fehlt.– Ben Voigt
27. April 2015 um 22:06 Uhr
@jave.web: Das war genau mein Problem. Danke! Ich bin neu bei cpp, aber soweit ich das beurteilen kann, hatte ich genau das Problem, von dem Ben Voigt sagt, dass es ziemlich selten war. Ich denke, Ihre Lösung wäre eine großartige Antwort.
– RoG
20. Oktober 2016 um 6:42 Uhr
Sie können nützlich sein, ebenso wie viele Antworten auf Fragen, die als zu allgemein gekennzeichnet sind.
– Albert van der Horst
16. August 2019 um 19:07 Uhr
Ehrlich gesagt würde ich gerne ein reproduzierbares Minimalbeispiel als etwas sehen, das wir von den meisten neuen Benutzern verlangen. Ich meine nichts damit, es ist nur – wir können nicht erwarten, dass Menschen die Regeln befolgen, die wir uns nicht selbst auferlegen.
– Danilo
8. September 2019 um 18:54 Uhr