Multiply defined symbols


  • Mod

    Das Problem sind nicht deine Include-Guards sondern die mehrfache Definition der Symbole weil du sie fälschlicherweise in deiner Headerdatei definierst. Pack die func1 bis func3 in eine eigene cpp Datei wie es sich gehört! In den Header gehören nur die Deklarationen.



  • Nachdem "test_lib.h" 1x inkludiert wurde, sollte "HEADER_TEST_LIB_H" definiert sein. Wenn "test_lib.h" das nächste Mal inkludiert wird, ist "HEADER_TEST_LIB.h" bereits definiert und der Code dazwischen sollte der Compiler eigentlich nicht mehr sehen.

    @SeppJ
    Okay, hier ist das tatsächlich so. Ich denke mir ein besseres Beispiel aus.


  • Mod

    FrEEzE2046 schrieb:

    Nachdem "test_lib.h" 1x inkludiert wurde, sollte "HEADER_TEST_LIB_H" definiert sein. Wenn "test_lib.h" das nächste Mal inkludiert wird, ist "HEADER_TEST_LIB.h" bereits definiert und der Code dazwischen sollte der Compiler eigentlich nicht mehr sehen.

    Jede cpp Datei wird einzeln compiliert. Da wird sich zwischendurch nichts gemerkt.



  • SeppJ schrieb:

    Pack die func1 bis func3 in eine eigene cpp Datei wie es sich gehört! In den Header gehören nur die Deklarationen.

    func1 bis func3 sind eigentlich als static inline deklariert. Macht daher keinen Sinn sie in Header und cpp aufzutrennen.



  • FrEEzE2046 schrieb:

    SeppJ schrieb:

    Pack die func1 bis func3 in eine eigene cpp Datei wie es sich gehört! In den Header gehören nur die Deklarationen.

    func1 bis func3 sind eigentlich als static inline deklariert. Macht daher keinen Sinn sie in Header und cpp aufzutrennen.

    1. Sind sie nicht.
    2. Macht es keinen Sinn static-Funktionen in Headern zu definieren.


  • Janjan schrieb:

    1. Sind sie nicht.
    2. Macht es keinen Sinn static-Funktionen in Headern zu definieren.
    1. Ich sagte "eigentlich".
    2. Macht es überhaupt keinen Sinn static oder inline Funktionen in cpp Dateien zu definieren, wenn man sie extern verwenden will. Ist dir der Nutzen von static inline Funktionen überhaupt bekannt?


  • FrEEzE2046 schrieb:

    Macht es überhaupt keinen Sinn static oder inline Funktionen in cpp Dateien zu definieren, wenn man sie extern verwenden will.

    Ich habe lange überlegt, wie man diese Frage beantworten soll. Ich habe den Eindruck, dass static und inline nicht 100%ig verstanden worden sind. Die Frage ist auch komisch. "static" in diesem Kontext ist so ziemlich das Gegenteil von "extern". Du kannst also eine in einer cpp-Datei definierten static-Funktion nicht direkt von anderen Übersetzungseinheiten aus aufrufen, da die Funktion (dank static) eine interne Bindung besitzt. Wenn Du eine Funktion als inline deklarierst, in der sie auch definiert wird, dann muss die Definition auch in allen anderen ÜEs, die eine Deklaration enthalten, definiert werden. So will es das Gesetz (C++ Standard). Falls Du, was die Begriffe "Deklaration" und "Definition" angeht, nicht fit bist, solltest Du nachgucken, was damit gemeint ist.

    FrEEzE2046 schrieb:

    Ist dir der Nutzen von static inline Funktionen überhaupt bekannt?

    Ist Dir der Nutzen von "static" und "inline" bekannt?

    Probier mal folgendes:

    // header.hpp
    #ifndef HEADER_HPP
    #define HEADER_HPP
    
    typedef int fun_type();
    
    inline int dings() { static int z=0; return z++; }
    
    #endif
    
    // ue1.cpp
    #include <iostream>
    #include "header.hpp"
    
    using namespace std;
    
    fun_type* main2();
    
    int main()
    {
      cout << dings() << '\n';
      cout << dings() << '\n';
      cout << dings() << '\n';
      fun_type* ptr = main2();
      cout << "Adresen stimmen "
        << (ptr != &dings ? "nicht " : "")
        << "ueberein.\n";
    }
    
    // ue2.cpp
    #include <iostream>
    #include "header.hpp"
    
    using namespace std;
    
    fun_type* main2()
    {
      cout << dings() << '\n';
      cout << dings() << '\n';
      cout << dings() << '\n';
      return &dings;
    }
    
    > g++ -o test1 *.cpp && ./test1
    0
    1
    2
    3
    4
    5
    Adressen stimmen ueberein.
    

    Wenn ich jetzt in header.hpp ein static vor das inline setze, sieht das ganze so aus:

    > g++ -o test1 *.cpp && ./test1
    0
    1
    2
    0
    1
    2
    Adressen stimmen nicht ueberein.
    

    Wie Du siehst, spielt static schon eine Rolle -- aber möglicherweise nicht die, die Du im Sinn hattest.

    static auf "globaler Ebene" --> interne Bindung --> jede ÜE hat seine eigene Funktion mit dem Namen --> jede Funktion hat eigene statische Daten und eine eigene Adresse.

    Trotzdem lässt sich das Programm ohne static kompilieren und "linken". Die ODR (one definition rule) von C++ erlaubt Mehrfachdefinitionen von inline-Funktionen (u.a.), sofern diese in versch ÜEs auftauchen und "identisch" sind (für noch eine näher zu definierende Bedeutung von "identisch"...). ÜE = Übersetzungseinheit = das, was der Präprozessor dem Compiler zum Fraß vorwirft. Da der Präprozessor zB den Inhalt der Datei header.hpp für #include "header.hpp" einsetzt, gibt es also 2 ÜEs, die die Definition der inline-Funktion enthalten.

    In C99 ist es üblich, bei inline-Funktionen ein static dazu zu schreiben. C99 besitzt auch (leider) keine vergleichbare Ausnahmeregelung für inline-Fuktionen.



  • krümelkacker schrieb:

    FrEEzE2046 schrieb:

    Macht es überhaupt keinen Sinn static oder inline Funktionen in cpp Dateien zu definieren, wenn man sie extern verwenden will.

    Ich habe lange überlegt, wie man diese Frage beantworten soll. Ich habe den Eindruck, dass static und inline nicht 100%ig verstanden worden sind. Die Frage ist auch komisch. "static" in diesem Kontext ist so ziemlich das Gegenteil von "extern". Du kannst also eine in einer cpp-Datei definierten static-Funktion nicht direkt von anderen Übersetzungseinheiten aus aufrufen, da die Funktion (dank static) eine interne Bindung besitzt. Wenn Du eine Funktion als inline deklarierst, in der sie auch definiert wird, dann muss die Definition auch in allen anderen ÜEs, die eine Deklaration enthalten, definiert werden. So will es das Gesetz (C++ Standard). Falls Du, was die Begriffe "Deklaration" und "Definition" angeht, nicht fit bist, solltest Du nachgucken, was damit gemeint ist.

    Du hättest länger darüber nachdenken sollen, wie ich das gemeint habe 😉
    Das war keine "Frage" von mir, sondern eine ironische Bemerkung zum Post von Janjan. Ich wollte damit ausdrücken, dass ich diese Funktion "extern", im Sinne von in einer anderen *.cpp, verwenden möchte; diese aber direkt an der Stelle der Verwendung geinlined werden soll und selbst nicht angelegt werden muss. Dies widersprach aber dem Kommentar von genanntem Poster.



  • FrEEzE2046 schrieb:

    [...] Ich wollte damit ausdrücken, dass ich diese Funktion "extern", im Sinne von in einer anderen *.cpp, verwenden möchte; diese aber direkt an der Stelle der Verwendung geinlined werden soll und selbst nicht angelegt werden muss. [...]

    Damit ein Compiler eine Funktion "inlinen" kann, sollte er die Definition kennen. Es gibt zwar auch solche schlauen Erfindungen wie "Link-Time-Optimization", aber das ist nicht die Regel. Wenn Deine Toolchain kein LTO kann, bleibt dir nichts anderes übrig, als die Definition der Funktion in all den Übersetzungeinheiten einzubauen, in denen Du die Funktion benutzen willst. Das macht man typischerweise durch Header-Dateien. Hierbei hilft Dir das inline-Schlüsselwort, weil Definitionen von inline-Funktionen mehrfach in einem Programm auftauchen dürfen (max. eine Definition derselben Funktion pro ÜE). Im Idealfall wird die Funktion immer "geinlined" und nie als eigenes Symbol in den Objekt-Dateien angelegt. Im schlechtesten Fall, legt der Compiler ein Symbol für die Funktion je ÜE an, führt kein Inlining durch, und der Linker schmeißt alle Definitionen bis auf eine einfach raus, ohne zu meckern. Das Schlüsselwort static braucht man dafür in C++ nicht.

    Bei Spezialisierungen von Funktions-Templates, die nicht inline sind, gilt das gleiche wie für normale Funktionen. Die ODR lässt insgesamt nur eine Definition zu. Deswegen bekommst Du auch die "already defined"-Fehlermeldung bei den Spezialisierungen.

    Das ganze hat nichts mit Include-Guards zu tun. Du musst nur das Separate-Compilation-Konzept und die ODR verstehen.

    Ist noch etwas unklar?



  • krümelkacker schrieb:

    Ist noch etwas unklar?

    Mir nicht, dir schon. Ich habe nie etwas anderes behauptet. Genau aus deiner Begründung heraus, habe ich ja schon am Anfang geschrieben, dass ich die Funktionen deshalb in den Headern habe.



  • // test_lib.hpp
    #ifndef HEADER_TEST_LIB_HPP
    #define HEADER_TEST_LIB_HPP
    #define TEST_INTERNAL_USE
    
    namespace TestLib {
    
    #include "test_header.hpp"
    #include "class_header.hpp"
    
    }	// namespace TestLib
    
    #undef TEST_INTERNAL_USE
    #endif
    
    // test_header.hpp
    #if !defined(HEADER_TEST_HPP) && defined(TEST_INTERNAL_USE)
    #define HEADER_TEST_HPP
    
    namespace Test {
    
    	namespace Detail {
    extern "C" {
    	#include "test_header.h"
    }
    	}	// namespace Detail
    
    template<typename T>
    T func(T);
    
    template<> char  func(char  value)	{ return Detail::func1(value); }
    template<> short func(short value)	{ return Detail::func2(value); }
    template<> int   func(int   value)	{ return Detail::func3(value); }
    
    }	// namespace Test
    
    #endif
    
    // test_header.cpp
    #include "test_header.h"
    
    extern "C" 
    {
    	char func1(char value)		{ return value; }
    	short func2(short value)	{ return value; }
    	int func3(int value)		{ return value; }
    }
    
    // class_header.hpp
    #if !defined(HEADER_CLASS_H) && defined(TEST_INTERNAL_USE)
    #define HEADER_CLASS_H
    
    namespace Class {
    
    class TestClass
    {
    private:
    	int x, y;
    
    public:
    	TestClass(int a, int b);
    };
    
    }	// namespace Class
    
    #endif
    
    // class_header.cpp
    #define TEST_INTERNAL_USE
    #include "class_header.hpp"
    
    namespace Class {
    
    TestClass::TestClass(int a, int b) : x(a), y(b) {}
    
    }	// namespace class
    
    #undef TEST_INTERNAL_USE
    
    // main.cpp
    #include "test_lib.h"
    
    int main()
    {
    	return 0;
    }
    

    Der Fehler lag am falschen include von "test_lib.hpp" in "class_header.cpp".



  • FrEEzE2046 schrieb:

    krümelkacker schrieb:

    Ist noch etwas unklar?

    Mir nicht, dir schon.

    Ein einfaches "Nein." hätte gereicht. Offensichlich war mir nicht klar, ob das Problem noch besteht oder nicht. Das kann man auch ganz leicht begründen: Du bist nicht wirklich auf die Antworten eingegangen und hast auch keine "Problem gelöst!"-Nachricht gepostet. Das, was Du geschrieben hast, wirkte so, als hättest Du mindestens eins der drei folgenden Konzepte nicht verstanden: separate compilation, include guards, one definition rule. Das ist ja kein Beinbruch. Aber frech werden, muss man deswegen auch nicht.



  • Okay, Problem gelöst und ihr habt euch eure Meinung gesagt - damit sollte der öffentliche Diskussionsbedarf gestillt sein. Private Differenzen sollten ab hier wie immer auch privat geklärt werden, deshalb bitte nurnoch konstruktive Beiträge zum Thema. Danke.


Anmelden zum Antworten