[c++] Was ist eine nicht definierte Referenz / ein nicht aufgelöster externer Symbolfehler und wie kann ich sie beheben?



Answers

Klassenmitglieder:

Ein reiner virtual Destruktor benötigt eine Implementierung.

Um einen Destruktor rein zu deklarieren, müssen Sie ihn noch definieren (im Gegensatz zu einer normalen 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 Basisklassendestruktoren aufgerufen werden, wenn das Objekt implizit zerstört wird. Daher ist eine Definition erforderlich.

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

Dies ist vergleichbar mit nicht virtual Methoden ohne Definition, mit dem zusätzlichen Argument, dass die reine Deklaration eine Dummy-V-Tabelle generiert und Sie den Linker-Fehler erhalten könnten, 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, deklariere 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 inline sein, in der Klassendefinition selbst:

struct A
{ 
    ~A() {}
};

oder draußen:

A::~A() {}

Wenn die Implementierung außerhalb der Klassendefinition, jedoch in einem Header ist, 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 es, zu 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 werden :

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

Ein Initialisierer kann für ein static const Datenelement des Integral- oder Aufzählungstyps innerhalb der Klassendefinition bereitgestellt werden; Für die odr-Verwendung dieses Members ist jedoch weiterhin eine Namensbereichsdefinition erforderlich, wie oben beschrieben. C ++ 11 ermöglicht die Initialisierung innerhalb der Klasse für alle static const Datenelemente.

Question

Was sind nicht definierte Referenzen / ungelöste externe Symbolfehler? Was sind häufige Ursachen und wie kann man sie beheben / verhindern?

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




Clean and rebuild

A "clean" of the build can remove the "dead wood" that may be left lying around from previous builds, failed builds, incomplete builds and other build system related build issues.

In general the IDE or build will include some form of "clean" function, but this may not be correctly configured (eg in a manual makefile) or may fail (eg the intermediate or resultant binaries are read-only).

Once the "clean" has completed, verify that the "clean" has succeeded and all the generated intermediate file (eg an automated makefile) have been successfully removed.

This process can be seen as a final resort, but is often a good first step ; especially if the code related to the error has recently been added (either locally or from the source repository).




Was ist eine "undefinierte Referenz / ungelöstes äußeres Symbol"?

Ich werde versuchen zu erklären, was eine "undefinierte Referenz / ein ungelöstes externes Symbol" ist.

hinweis: ich benutze g ++ und Linux und alle beispiele 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 Assemblerphase haben wir eine Objektdatei, die alle zu exportierenden Symbole enthält. Schau dir die Symbole an

$ readelf --symbols src1.o
  Num:    Value          Size Type    Bind   Vis      Ndx Name
     5: 0000000000000000     4 OBJECT  LOCAL  DEFAULT    4 _ZL14local_var_name # [1]
     9: 0000000000000000     4 OBJECT  GLOBAL DEFAULT    3 global_var_name     # [2]

Ich habe einige Zeilen aus der Ausgabe abgelehnt, weil sie nicht wichtig sind

Wir sehen also folgende Symbole zum Exportieren.

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

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

Verknüpfen Sie unsere Objektdateien

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

und führe es aus

$ ./prog
123

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

// src2.cpp
extern "C" int printf (const char*, ...);

extern int global_var_name;
extern int local_var_name;

void print ()
{
    printf("%d%d\n", global_var_name, local_var_name);
}

und erstellen Sie eine Objektdatei neu

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

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

$ g++ src1.o src2.o -o prog
src2.o: In function `print()':
src2.cpp:(.text+0x6): undefined reference to `local_var_name'
collect2: error: ld returned 1 exit status

Es ist passiert, weil unser local_var_name statisch ist, dh er ist für andere Module nicht sichtbar. 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

Also, wir haben gesehen, dass es keine Bezeichnung für local_var_name gibt, deshalb hat linker es nicht gefunden. Aber wir sind Hacker :) und wir können es beheben. Öffne src1.s in deinem Texteditor und ändere es

.local  _ZL14local_var_name
.comm   _ZL14local_var_name,4,4

zu

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

Dh du solltest wie folgt 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 setzen den Wert auf 456789. Versuchen Sie, eine Objektdatei daraus zu erstellen

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

ok, siehe Realdelf-Ausgabe (Symbole)

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

Jetzt hat local_var_name Bind GLOBAL (war LOKAL)

Verknüpfung

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

und führe es aus

$ ./prog 
123456789

ok, wir hacken es :)

Als Ergebnis - ein "undefinierter Verweis / nicht aufgelöster externer Symbolfehler" tritt auf, wenn der Linker globale Symbole in den Objektdateien nicht finden kann.




Wenn alles andere fehlschlägt, kompilieren Sie erneut.

Ich war kürzlich in der Lage, einen unaufgelösten externen Fehler in Visual Studio 2012 loszuwerden, indem ich einfach die beleidigende Datei neu kompilierte. Als ich neu baute, ging der Fehler weg.

Dies geschieht normalerweise, 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, mit denen man anfangen könnte. Wenn Sie versuchen, A zu kompilieren, schlägt der Verbindungsschritt fehl, da B.lib nicht gefunden werden kann. A.lib wird generiert, aber keine dll. Dann kompilieren Sie B, was erfolgreich ist und B.lib generiert. Das erneute Kompilieren von A funktioniert jetzt, weil B.lib jetzt gefunden wird.




Linked .lib file is associated to a .dll

Ich hatte das gleiche Problem. Say i have projects MyProject and TestProject. I had effectively linked the lib file for MyProject to the TestProject. However, this lib file was produced as the DLL for the MyProject was built. Also, I did not contain source code for all methods in the MyProject, but only access to the DLL's entry points.

To solve the issue, i built the MyProject as a LIB, and linked TestProject to this .lib file (i copy paste the generated .lib file into the TestProject folder). I can then build again MyProject as a DLL. It is compiling since the lib to which TestProject is linked does contain code for all methods in classes in MyProject.




When your include paths are different

Linker errors can happen when a header file and its associated shared library (.lib file) go out of sync. Let me explain.

How do linkers work? The linker matches a function declaration (declared in the header) with its definition (in the shared library) by comparing their signatures. You can get a linker error if the linker doesn't find a function definition that matches perfectly.

Is it possible to still get a linker error even though the declaration and the definition seem to match? Ja! They might look the same in source code, but it really depends on what the compiler sees. Essentially you could end up with a situation like this:

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

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

Note how even though both the function declarations look identical in source code, but they are really different according to the compiler.

You might ask how one ends up in a situation like that? Include paths of course! If when compiling the shared library, the include path leads to header1.h and you end up using header2.h in your own program, you'll be left scratching your header wondering what happened (pun intended).

An example of how this can happen in the real world is explained below.

Further elaboration with an example

I have two projects: graphics.lib and main.exe . Both projects depend on common_math.h . Suppose the library exports the following function:

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

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

And then you go ahead and include the library in your own project.

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

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

Boom! You get a linker error and you have no idea why it's failing. The reason is that the common library uses different versions of the same include common_math.h (I have made it obvious here in the example by including a different path, but it might not always be so obvious. Maybe the include path is different in the compiler settings).

Note in this example, the linker would tell you it couldn't find draw() , when in reality you know it obviously is being exported by the library. You could spend hours scratching your head wondering what went wrong. The thing is, the linker sees a different signature because the parameter types are slightly different. In the example, vec3 is a different type in both projects as far as the compiler is concerned. This could happen because they come from two slightly different include files (maybe the include files come from two different versions of the library).

Debugging the linker

DUMPBIN is your friend, if you are using Visual Studio. I'm sure other compilers have other similar tools.

The process goes like this:

  1. Note the weird mangled name given in the linker error. (eg. draw@graphics@XYZ).
  2. Dump the exported symbols from the library into a text file.
  3. Search for the exported symbol of interest, and notice that the mangled name is different.
  4. Pay attention to why the mangled names ended up different. You would be able to see that the parameter types are different, even though they look the same in the source code.
  5. Reason why they are different. In the example given above, they are different because of different include files.

[1] By project I mean a set of source files that are linked together to produce either a library or an executable.

EDIT 1: Rewrote first section to be easier to understand. Please comment below to let me know if something else needs to be fixed. Vielen Dank!




Template-Implementierungen nicht sichtbar

Nichtspezialisierte Vorlagen müssen ihre Definitionen für alle Übersetzungseinheiten sichtbar machen, 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 impl darin, eine impl Datei zu haben, die Sie am Ende des Headers impl , der die Vorlage deklariert. Eine häufige Situation ist:

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

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

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

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

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

Für weitere Erklärungen und eine andere mögliche Lösung (explizite Instanziierung) siehe diese Frage und Antwort .




Befriending templates...

Given the code snippet of a template type with a friend operator (or function);

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

The operator<< is being declared as a non-template function. For every type T used with Foo , there needs to be a non-templated operator<< . For example, if there is a type Foo<int> declared, then there must be an operator implementation as follows;

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

Since it is not implemented, the linker fails to find it and results in the error.

To correct this, you can declare a template operator before the Foo type and then declare as a friend, the appropriate instantiation. The syntax is a little awkward, but is looks as follows;

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

The above code limits the friendship of the operator to the corresponding instantiation of Foo , ie the operator<< <int> instantiation is limited to access the private members of the instantiation of Foo<int> .

Alternatives include;

  • Allowing the friendship to extend to all instantiations of the templates, as follows;

    template <typename T>
    class Foo {
        template <typename T1>
        friend std::ostream& operator<<(std::ostream& os, const Foo<T1>& a);
        // ...
    };
    
  • Or, the implementation for the operator<< can be done inline inside the class definition;

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

Note , when the declaration of the operator (or function) only appears in the class, the name is not available for "normal" lookup, only for argument dependent lookup, from cppreference ;

A name first declared in a friend declaration within class or class template X becomes a member of the innermost enclosing namespace of X, but is not accessible for lookup (except argument-dependent lookup that considers X) unless a matching declaration at the namespace scope is provided...

There is further reading on template friends at cppreference and the C++ FAQ .

Code listing showing the techniques above .

As a side note to the failing code sample; g++ warns about this as follows

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)




Microsoft bietet ein #pragma an, um die richtige Bibliothek zu Verknüpfungszeit 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.




Angenommen, Sie haben ein großes in C ++ geschriebenes Projekt, das tausend .cpp-Dateien und tausend .h-Dateien enthält. Und sagen wir, das Projekt hängt auch von zehn statischen Bibliotheken ab. Lassen Sie uns sagen, wir sind auf Windows und wir bauen unser Projekt in Visual Studio 20xx. When you press Ctrl + F7 Visual Studio to start compiling the whole solution ( suppose we have just one project in the solution )

What's the meaning of compilation ?

  • Visual Studio search into file .vcxproj and start compiling each file which has the extension .cpp. Order of compilation is undefined.So you must not assume that the file main.cpp is compiled first
  • If .cpp files depends on additional .h files in order to find symbols that may or may not be defined in the file .cpp
  • If exists one .cpp file in which the compiler could not find one symbol, a compiler time error raises the message Symbol x could not be found
  • For each file with extension .cpp is generated an object file .o and also Visual Studio writes the output in a file named ProjectName.Cpp.Clean.txt which contains all object files that must be processed by the linker.

The Second step of compilation is done by Linker.Linker should merge all the object file and build finally the output ( which may be an executable or a library)

Steps In Linking a project

  • Parse all the object files and find the definition which was only declared in headers ( eg: The code of one method of a class as is mentioned in previous answers, or event the initialization of a static variable which is member inside a class)
  • If one symbol could not be found in object files he also is searched in Additional Libraries.For adding a new library to a project Configuration properties -> VC++ Directories -> Library Directories and here you specified additional folder for searching libraries and Configuration properties -> Linker -> Input for specifying the name of the library. -If the Linker could not find the symbol which you write in one .cpp he raises a linker time error which may sound like error LNK2001: unresolved external symbol "void __cdecl foo(void)" (?foo@@YAXXZ)

Observation

  1. Once the Linker find one symbol he doesn't search in other libraries for it
  2. The order of linking libraries does matter .
  3. If Linker finds an external symbol in one static library he includes the symbol in the output of the project.However, if the library is shared( dynamic ) he doesn't include the code ( symbols ) in output, but Run-Time crashes may occur

How To Solve this kind of error

Compiler Time Error :

  • Make sure you write your c++ project syntactical correct.

Linker Time Error

  • Define all your symbol which you declare in your header files
  • Use #pragma once for allowing compiler not to include one header if it was already included in the current .cpp which are compiled
  • Make sure that your external library doesn't contain symbols that may enter into conflict with other symbols you defined in your header files
  • When you use the template to make sure you include the definition of each template function in the header file for allowing the compiler to generate appropriate code for any instantiations.



Since people seem to be directed to this question when it comes to linker errors I am going to add this here.

One possible reason for linker errors with GCC 5.2.0 is that a new libstdc++ library ABI is now chosen by default.

If you get linker errors about undefined references to symbols that involve types in the std::__cxx11 namespace or the tag [abi:cxx11] then it probably indicates that you are trying to link together object files that were compiled with different values for the _GLIBCXX_USE_CXX11_ABI macro. This commonly happens when linking to a third-party library that was compiled with an older version of GCC. If the third-party library cannot be rebuilt with the new ABI then you will need to recompile your code with the old ABI.

So if you suddenly get linker errors when switching to a GCC after 5.1.0 this would be a thing to check out.




undefinierter Verweis auf WinMain@16 oder ähnliche "ungewöhnliche" main() -Eintragspunkt-Referenz (speziell für visual-studio ).

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

Wenn Ihre IDE Plain Console Projects unterstützt, sollten Sie diesen Projekttyp anstelle eines Windows-Anwendungsprojekts auswählen.




Deklariert, hat aber keine Variable oder Funktion definiert.

Eine typische Variablendeklaration ist

extern int x;

Da dies nur eine Deklaration ist, wird eine einzige Definition benötigt. Eine entsprechende Definition wäre:

int x;

Das folgende Beispiel würde einen Fehler erzeugen:

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

Ähnliche Bemerkungen gelten für Funktionen. Eine Funktion zu deklarieren, ohne sie zu definieren, führt zu dem Fehler:

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

Achten Sie darauf, dass die von Ihnen implementierte Funktion genau der von Ihnen angegebenen Funktion entspricht. Zum Beispiel 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)

Weitere Beispiele für Fehlanpassungen sind

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

Die Fehlermeldung vom Compiler gibt Ihnen oft die vollständige Deklaration der Variable oder Funktion, die deklariert, aber nie definiert wurde. Vergleichen Sie es genau mit der Definition, die Sie angegeben haben. Stellen Sie sicher, dass jedes Detail übereinstimmt.






Related