extern bei Funktionen doch nicht implizit?



  • Hallo,
    gerade war ich ein bisschen verstutzt, als folgendes Konstrukt zu einem Linkerfehler mit nicht aufgelöstem externen Verweis führte (Compiler MSVC 10):

    //foo.h
    #ifndef FOO_H
    #define FOO_H
    namespace Foo {
        inline void foo();
    }
    #endif
    
    //foo.cpp
    #include "foo.h"
    #include <iostream>
    
    inline void Foo::foo() {
        std::cout << "FOO" << std::endl;
    }
    
    //main.cpp
    #include "foo.h"
    
    int main() {
        Foo::foo();
        return 0;
    }
    

    Wenn ich in der foo.h nun aber ein extern vor die Funktionsdeklaration setze funktioniert alles tadellos. Muss ich jetzt wirklich vor jeder globalen Funktion ein extern setzen?



  • Nein, aber Inline-Funktionen können prinzipbedingt keine externe Linkage haben. Mit inline muss die Funktion in der gleichen Übersetzungseinheit (d.h. in jeder .cpp-Datei, die deinen Header einbindet) definiert sein. Das läuft darauf hinaus, dass die Definition von Inline-Funktionen ebenfalls im Header stehen muss.

    Lass also das Schlüsselwort weg, wenn du extern -Funktionen willst.



  • Ok, aber auch wenn ich das inline weglasse, kann der Compiler das mit der höchsten Optimierungsstufe doch trotzdem noch inlinen oder? Denn mit Klassenfunktionen funktioniert das ja auch...



  • Funktionen, die in anderen Übersetzungseinheiten definiert werden, kann kein Compiler inlinen. Auch nicht Klassenfunktionen. Wie soll er das auch machen, der Compiler kennt immer nur eine Übersetzungseinheit.

    Das kann höchstens der Linker, bzw. eine Stufe zwischen Compiler und Linker (Stichwort Linktime Codegeneration bei Microsoft, bei GCC gibt es etwas ähnliches).



  • Es gibt nur wenige Stellen, wo es wirklich sinnvoll ist, das inline explizit auszuschreiben. Der Grund ist ganz grob gesagt, dass Compiler, Omtimizer und Linker eh machen, was sie wollen. Sie inlinen nicht jede Funktion, die als inline deklariert wird, weil sie es manchmal besser wissen, dass sich das inlinen eben nicht lohnt. Dafür inlinen sie viele Funktionen, die nicht explizit inline deklariert wurden, weil sich der Overhead für den Aufruf nicht lohnt.

    Ich persönlich schreibe inline nur dann explizit hin, wenn ich eine nicht-template-Funktion im Header definieren möchte, weil ich entweder zu faul bin, für einen Einzeiler eine extra .cpp anzulegen, oder weil es sich um eine Header-Only Bibliothek handelt.



  • pumuckl schrieb:

    Ich persönlich schreibe inline nur dann explizit hin, wenn ich eine nicht-template-Funktion im Header definieren möchte, weil ich entweder zu faul bin, für einen Einzeiler eine extra .cpp anzulegen, oder weil es sich um eine Header-Only Bibliothek handelt.

    Bin ich erleichtert, dass ich nicht der einzige bin, der das so macht 🤡



  • 314159265358979 schrieb:

    Bin ich erleichtert, dass ich nicht der einzige bin, der das so macht 🤡

    Wobei ich mir den Punkt "Faulheit" auszutreiben versuche. 😉 Meist handelt man sich früher oder später ja eh noch die eine oder andere weitere Zeile/Funktion ein, und spätestens wenn für die Funktionsdefinition weitere Header benötigt werden, hört bei mir der Spaß auf.



  • WeltbildZerstört schrieb:

    //foo.h
    #ifndef FOO_H
    #define FOO_H
    namespace Foo {
        inline void foo();
    }
    #endif
    

    Wenn Du das Ding irgendwo einbindest und foo verwendest, ohne dass in derselben die Implementierung bekannt ist, verletzt Du damit die ODR (one definition rule). Die ODR sagt, dass, falls eine inline-Funktion benutzt wird, sie in derselben Übersetzungsheinheit auch definiert sein muss. Die ODR sagt auch, dass inline-Funktionen sowie Funktions-Templates mehrfach definiert werden dürfen, sofern die Definitionen in unterschiedlichen ÜEs auftauchen. Du darfst also die inline-Funktion direkt im Header definieren/implementieren. Wenn Du das nicht möchtest, musst Du hier das inline wegnehmen.

    WeltbildZerstört schrieb:

    Ok, aber auch wenn ich das inline weglasse, kann der Compiler das mit der höchsten Optimierungsstufe doch trotzdem noch inlinen oder?

    Eher nicht. Compiler arbeiten meistens ÜE-weise und wissen nicht, was in anderen ÜEs sonst noch steht. Eine solche Optimierung könnte man später beim Linken durchführen. So etwas gibt es auch schon und nennt sich LTO (link time optimization). Das können aber nicht viele und muss meistes extra eingeschaltet werden.

    WeltbildZerstört schrieb:

    Denn mit Klassenfunktionen funktioniert das ja auch...

    Funktionen, die Du in einer Klassendefinition definierst sind automatisch inline. Denn sonst würdest Du wieder ODR-Verletzungen bekommen.

    Schau in Deinem C++ Buch der Wahl nach zum Thema "one definition rule".

    ÜE = Übersetzungseinheit = quasi das, was der Präprozessor für eine *.cpp-Datei ausspuckt (nach includes, makro-ersetzung, etc)



  • krümelkacker schrieb:

    Die ODR sagt auch, dass inline-Funktionen sowie Funktions-Templates mehrfach definiert werden dürfen, sofern die Definitionen in unterschiedlichen ÜEs auftauchen.

    Wichtig ist hierbei noch zu erwähnen, dass die Definitionen in den verschiedenen ÜEs "gleichwertig" sein müssen.
    Gleichwertig heißt dabei, dass natürlich whitespaces verschieden gestaltet werden dürfen (sieht der Compiler eh nichts von), aber auch, dass z.B. Variablen andere Namen haben dürfen. Folgendes geht also durchaus:

    //A.h
    inline void foo(const int& i);
    
    //A.cpp
    #include <iostream>
    #include "A.h"
    inline void foo(const int& y)
    {
      int z = 6;
      using std::cout;
      cout << y*z << '\n';
    }
    
    //main.cpp
    #include "A.h"
    #include <iostream>
    
    #define LINEBREAK '\n'
    
    void foo(const int& myIntParam) {
      int other = 6;
    
      //just output the product
      std::cout << (myIntParam*other)
           << LINEBREAK;
    }
    

Log in to reply