[gelöst] C++ programmieren unter Anjuta (Debian 6)



  • Es gibt eben die "One Definition Rule". Du darfst für jedes Symbol (Variable, Funktion, Klasse, Struktur, etc.) nur eine Definition (also Implementierung haben) aber beliebig viele Deklarationen. Der Linker löst dann am Ende ohne Probleme die Symbole (LaTeX mäßig eben die ?? bei den Verweisen) auf.

    Bsp

    // foo.hpp
    #ifndef FOO_HPP
    #define FOO_HPP // Include guards um Mehrfacheinbindung zu verhindern
    
    extern void foo(); // Deklaration
    
    #endif
    
    // foo.cpp
    #include "foo.hpp" // Beachte: Keine absoluten Pfade! Niemals nie!
    #include <iostream>
    
    void foo() { // Definition
      std::cout << "Hello World\n";
    }
    
    #include "foo.hpp"
    
    int main() {
      foo(); // Der Compiler generiert hier sozusagen "??" und der Linker löst das auf in einen Aufruf von foo
    }
    

    g++ -Wall -Wextra -pedantic -ansi -c foo.cpp
    g++ -Wall -Wextra -pedantic -ansi -c main.cpp
    g++ foo.o main.o -o foo
    ./foo
    Hello World



  • Ich denke so langsam verstehe ich.

    rüdiger schrieb:

    #include "foo.hpp"
    
    int main() {
      foo(); // Der Compiler generiert hier sozusagen "??" und der Linker löst das auf in einen Aufruf von foo
    }
    

    Was mir hier erst auffällt, dass du nur header Dateien verwendest!

    Das heißt, wenn ich in einem cpp Code eine Variable oder Methode verwenden möchte, dass du muss ich die entsprechende header Datei included sein, in der die Deklaration steht.
    Die Deklaration alleine reicht dem Compiler allerdings noch nicht, da erst die Definition die Information liefert was die Methode auch tut und somit den Compiler zufriedenstellt.

    Ich habe nochmal ein Minimalbeispiel erstellt.

    // linear_function.h
    double linear_function(double x);
    

    und

    // linear_function.cpp
    #include "linear_function.h"
    
    double linear_function(double x)
    {
    double m = 2;
    double b = -2;
       return m*x + b;
    }
    

    Das ganze wird nun hier verwendet

    // composition_function.h
    double composition_function(double x);
    

    Und schließlich die Definition, wobei ich nur die header Datei verwende!

    // composition_function.cpp
    #include "composition_function.h"
    #include "linear_function.h"
    
    double composition_function(double x)
    {
       return x * linear_function(x);
    }
    

    Jetzt bin ich aber doch ein wenig verwirrt. Ich kompiliere zunächst die compositin_functin.cpp

    g++ -Wall -pedantic -ansi -c composition_functin.cpp
    

    Und bekomme keine Fehlermeldung! Wieso das denn? Ist es die Option -c, welche darauf hinweist, dass noch nicht gelinkt werden soll, sprich noch keine Gedanken über Abhängigkeit gemacht werden sollen?
    Der Kompiler weiß durch include "linear_function.h", dass die verwendete Referenz zwar deklariert ist, interessiert sich aber noch nicht für deren Definition?

    Ich habe include "linear_function.h" mal herausgenommen mittels //, dann meckert er

    :~/Cpp/Minimalbeispiele$ g++ -Wall -pedantic -ansi -c composition_function.cpp
    composition_function.cpp: In function ‘double composition_function(double)’:
    composition_function.cpp:7: error: ‘linear_function’ was not declared in this scope
    

    Aha! Sehr interessant. Also schnell wieder hinein.

    Was mich jetzt noch wundert, warum ich nicht linear_function.o und composition_function.o linken kann?

    :~/Cpp/Minimalbeispiele$ g++ linear_function.o composition_function.o -o composition_function
    /usr/lib/gcc/x86_64-linux-gnu/4.4.5/../../../../lib/crt1.o: In function `_start':
    (.text+0x20): undefined reference to `main'
    collect2: ld returned 1 exit status
    

    Ich nehem an, es liegt daran, dass in C/C++ alles in einer int main(){} Umgebung liegen muss? Ist das einfach Konvention? Also schnell noch eine main file geschrieben:

    #include <iostream>
    
    #include "linear_function.h"
    #include "composition_function.h"
    
    using namespace std;
    
    int main()
    {
       double value = 3;
    
       cout << "Result = " << composition_function(value) << endl;
    
       return 0;
    }
    

    Kompiliert und dann funktioniert auch das linken wieder und ich bekomme ein Ergebnis:

    :~/Cpp/Minimalbeispiele$ g++ -Wall -pedantic -ansi -c main.cpp
    :~/Cpp/Minimalbeispiele$ g++ main.o linear_function.o composition_function.o -o main
    :~/Cpp/Minimalbeispiele$ ./main
    Result = 12
    :~/Cpp/Minimalbeispiele$
    

    Spielt die Abhängigkeit beim Linken eigentlich eine Rolle, oder schafft der Kompiler das von alleine?

    Bin ich langsam vom Verständnis her auf dem richtigen Weg? 🙂

    Viele Grüße und danke für eure Hilfe, 🙂
    Klaus.



  • Das heißt, wenn ich in einem cpp Code eine Variable oder Methode verwenden möchte, dass du muss ich die entsprechende header Datei included sein, in der die Deklaration steht.
    Die Deklaration alleine reicht dem Compiler allerdings noch nicht, da erst die Definition die Information liefert was die Methode auch tut und somit den Compiler zufriedenstellt.

    Die Deklaration muss nicht in einem Header stehen

    // bar.cpp
    #include <iostream>
    
    void foo() {
      std::cout << "Hallo Welt\n";
    }
    
    // foo.cpp
    extern void foo();
    
    int main() {
      foo();
    }
    

    geht genauso. Und der Compiler braucht nur die Deklaration. Erst der Linker muss dann wissen wo er die Definition finden kann, damit der den Aufruf von foo an die richtige Stelle setzt.

    Ist es die Option -c, welche darauf hinweist, dass noch nicht gelinkt werden soll, sprich noch keine Gedanken über Abhängigkeit gemacht werden sollen?

    Ja

    Was mich jetzt noch wundert, warum ich nicht linear_function.o und composition_function.o linken kann?

    Ein ausführbares Programm braucht eine main-Funktion.



  • Hi,

    rüdiger schrieb:

    Und der Compiler braucht nur die Deklaration. Erst der Linker muss dann wissen wo er die Definition finden kann, damit der den Aufruf von foo an die richtige Stelle setzt.

    Alles klar.

    Aber was mich nach wie vor wundert: Cpp Dateien einbinden oder nicht?

    Ich bin immernoch irritiert von Bashars Aussage, der da schreibt:

    Nein, du bindest normalerweise keine cpp-Dateien ein. (Wo lernt man sowas?) Du compilierst alle cpp-Dateien getrennt. Inkludieren tust du nur Headerdateien.

    Aber bei dem Thread zum Minimalbeispiel der GSL binden wir cpp Dateien ein und es hat niemanden gestört.

    Ich kann das Beispiel auch mal 'rüberholen':

    Klaus82 schrieb:

    rng.h

    double get_random_number();
    

    Dann das ganze in eine cpp-Datei eingebunden:
    rng.cpp

    double get_random_number() 
    {
    const gsl_rng_type* T;
    gsl_rng* r;
    gsl_rng_env_setup();
    
    T = gsl_rng_default;
    r = gsl_rng_alloc (T);
    
    return gsl_rng_uniform (r);
    
    gsl_rng_free (r);
    }
    

    Schließlich habe ich das ganze in ein Hauptprogramm eingebunden, worin ich schließlich eine Zufallszahl verwenden möchte:
    main.cpp

    #include<iostream>
    #include<stdio.h>
    // GSL
    #include</usr/local/include/gsl/gsl_rng.h>
    // personal header files
    #include "rng.h"
    #include "rng.cpp"
    
    using namespace std;
    
    int main(){//
    
    cout << "Random number in [0,1[ : " << get_random_number() << endl;
    
    return 0;
    }
    

    Gruß,
    Klaus.



  • Klaus82 schrieb:

    Aber was mich nach wie vor wundert: Cpp Dateien einbinden oder nicht?

    Die Frage hab ich dir schon beantwortet.

    Ich bin immernoch irritiert von Bashars Aussage, der da schreibt:

    Nein, du bindest normalerweise keine cpp-Dateien ein. (Wo lernt man sowas?) Du compilierst alle cpp-Dateien getrennt. Inkludieren tust du nur Headerdateien.

    Achso, du wartest darauf, dass jemand dir die Antwort gibt, die du hören willst.

    Aber bei dem Thread zum Minimalbeispiel der GSL binden wir cpp Dateien ein und es hat niemanden gestört.

    rüdiger dürfte das einfach übersehen haben, wofür ja auch seine Antwort, "Du hast vergessen rng.cpp zu kompilieren und zu linken!" spricht. Sonst hat sich dazu keiner geäußert.



  • Hi,

    Bashar schrieb:

    Achso, du wartest darauf, dass jemand dir die Antwort gibt, die du hören willst.

    😕

    Bashar schrieb:

    Aber bei dem Thread zum Minimalbeispiel der GSL binden wir cpp Dateien ein und es hat niemanden gestört.

    rüdiger dürfte das einfach übersehen haben, wofür ja auch seine Antwort, "Du hast vergessen rng.cpp zu kompilieren und zu linken!" spricht.

    Okay.

    Bashar schrieb:

    Sonst hat sich dazu keiner geäußert.

    Das ist ja nach wie vor der Punkte an dem ich knabbere. Rüdigers Aussage scheint zu bedeuten, dass es ein no-go ist cpp-Dateien einzubinden. In dem GSL Beispiel für Zufallszahlen wird es aber getan.

    Das lässt für mich den Schluß zu, dass es prinzipiell möglich ist nur scheinbar schlecher Programmierstil?

    Ich möchte es einfach einordnen können. 🙂

    Gruß,
    Klaus.



  • #include macht einfach nur eine Textersetzung. Das ist keine Magie hinter. Du kannst also jede Datei einbinden, die du einbinden willst. Auch die Trennung zwischen Header- und Codedatei ist rein willkürlich. Nur hält man sich normalerweise daran, dass man nur Header einbindet und in Header nur Deklarationen (ggf. inline Funktionen) schreibt. Bei dem Thema habe ich nur übersehen, dass du eine Codedatei eingebunden hast, sonst hätte ich dich darauf aufmerksam gemacht. Einbinden von Codedateien (also Definitionen) ist gefährlich, da man so schnell die "One Definition Rule" (ODR) verletzt. Außerdem ist ein großer Vorteil beim aufteilen in verschiedene Codedateien, dass der Compiler nicht jedes mal das ganze Projekt neu kompilieren muss und der Compilevorgang wesentlich schneller abläuft. Dies trifft jedoch nur zu, wenn man keine Codedateien inkludiert und die Header möglichst klein sind (immer nur das inkludieren was man braucht!)



  • Hi,

    rüdiger schrieb:

    Einbinden von Codedateien (also Definitionen) ist gefährlich, da man so schnell die "One Definition Rule" (ODR) verletzt.

    Okay, danke. Das klingt natürlich sehr einleuchtend. 🙂

    Also merken: In die Hauptdatei des Programms, wo int main(){} steht, nur header Dateien NAME.h includieren.

    Die zugehörigen cpp Dateien separat kompilieren.

    Wobei ich gerade mit den Abhängigkeiten ein wenig kämpfe, doch dazu mache ich mal einen neuen Thread auf.

    Gruß,
    Klaus.



  • rüdiger schrieb:

    #include "foo.hpp"
    
    int main() {
      foo(); // Der Compiler generiert hier sozusagen "??" und der Linker löst das auf in einen Aufruf von foo
    }
    

    Das ist falsch!

    Der Compiler kennt foo() als Deklaration, weil es ihm über die includierte Headerdatei bekannt gemacht wurde.
    Er weiß also den Funktionsnamen etwaige Funktionsparameter und den Rückgabetyp der Funktion und so wird das in die Objektdatei von main.o auch eingebaut.

    Der Linker führt dann nur noch main.o und foo.o zusammen.
    foo.o enthält dabei auch die Implementierung der Deklaration foo().



  • Klaus82 schrieb:

    Also merken: In die Hauptdatei des Programms, wo int main(){} steht, nur header Dateien NAME.h includieren.

    Falsch!
    Nicht nur in der Hauptdatei sondern in allen *.cpp Quellcodedateien nur header Dateien *.h includieren!

    Die zugehörigen cpp Dateien separat kompilieren.

    Makefiles benutzen!


Anmelden zum Antworten