Kurze Frage zu Begriffserklärung ( Von verschiedenen Pointern )



  • Hallo

    int *ptr(); <-- Gibt eine Adresse zurück oder doch einen Zeiger ?

    void (*ptr_func) (); <-- Kann eine Adresse von einer Funktion erhalten

    Kann man hierzu einfach sagen Pointer Funktion ? int *ptr();
    Oder wäre das der falsche Begriff dafür?

    #include <iostream>
    using namespace std;
    
    void ausgabe ();
    void (*ptr_func) ( ) ;
    
    int main ()
    {
    
    ptr_func =  ( void(*) () ) &ausgabe ;  // void (*ptr_func)(); zeigt auf die Adresse der Funktion  void ausgabe();
    
    (*ptr_func)();  // Die Funktion void ausgabe(); aufrufen (*ptr_func)(); zeigt auf die
                   // Funktion void ausgabe();
    
    system("PAUSE");
    }
    
    void ausgabe ( )
    {
     cout<<"ok";
    }
    

    Hier bekomme ich die Warnung:

    "Verdächtige Zeigerumwandlung" was bedeutet das 😕

    #include <iostream>
    using namespace std;
    
    int *ausgabe () ;
    
    int main ()
    {
    
    int ret;
    
    ret = *ausgabe();
    
    cout<<ret;
    
    system("PAUSE");
    }
    
    int *ausgabe ( )
    {
    int var=8;
    
    return &var;     
    }
    


  • ptr*** schrieb:

    int *ptr(); <-- Gibt eine Adresse zurück oder doch einen Zeiger ?

    Einen Zeiger, der aber meistens eine Adresse beinhaltet (oder NULL ).

    ptr*** schrieb:

    void (*ptr_func) (); <-- Kann eine Adresse von einer Funktion erhalten

    Ja (Funktionszeiger).

    ptr*** schrieb:

    Kann man hierzu einfach sagen Pointer Funktion ? int *ptr();

    Nein, zumindest nicht wenn du willst, dass man dich versteht.

    ptr*** schrieb:

    int *ausgabe ( )
    {
    int var=8;
    
    return &var;     
    }
    

    Du gibst die Adresse einer lokalen Variable zurück, die am Ende der Funktion ungültig wird, weil die Variable zerstört wird. Sowas solltest du nicht tun, da es undefiniertes Programmverhalten hervorrufen kann.



  • Wie benutzt man den so eine Funktion richtig ?

    int *ausgabe ( ) 
    { 
    int var=8; 
    
    return &var;     
    }
    


  • Was soll die Funktion denn tun?



  • int *ausgabe ( )
    {
    // Platz für einen int auf dem heap schaffen
    int* bla = new int; 
    
    // Irgendwas damit machen
    *bla = 8;
    
    // Pointer zurückgeben
    return bla;    
    }
    

    Der integer auf den bla zeigt wird mit new auf dem heap angelegt. Daher wird er nicht gelöscht wenn der Stack nach abarbeiten der Funktion bereinigt wird.
    Du musst dich aber später irgendwo mit delete um die Freigabe dieses Speichers kümmern (sonst Speicherleck!)

    int* ist eine Adresse. Diese Adresse wird kopiert und zurückgegeben

    irgendwo in deinem Programm:

    int *blubb = ausgabe();
    // gibt "8" aus
    cout << *blubb << endl;
    // wenn du den integer nicht mehr brauchst
    delete blubb;
    


  • Genau soll man es nicht machen. So hat man es in C gemacht, doch dieser Ansatz ist ganz schön fehleranfällig und in C++ durch RAII abgelöst.

    std::auto_ptr<int> ausgabe();
    

    Wobei es im Allgemeinen natürlich um komplexere Anwendungen als die Rückgabe eines int s geht, in denen mehrere Ablaufpfade oder Exceptions die Vorteile von RAII erst richtig zum Vorschein kommen lassen. Hier würde ich einfach einen int als Kopie zurückgeben, das ist ohnehin um einiges schneller.



  • [qoute]
    Hier würde ich einfach einen int als Kopie zurückgeben, das ist ohnehin um einiges schneller.
    [/quote]

    Wie meinst du das ?



  • Die dynamische Speicherverwaltung fällt weg.



  • doch dieser Ansatz ist ganz schön fehleranfällig und in C++ durch RAII abgelöst.

    Stimmt soweit. Jedoch kann man sich mit auto_ptr (oder auch mit shared_ptr) genauso gut in den Fuß schiessen wie mit new und delete (und meist sind diese Fehler noch schwerer zu debuggen).



  • antialias schrieb:

    Jedoch kann man sich mit auto_ptr (oder auch mit shared_ptr) genauso gut in den Fuß schiessen wie mit new und delete (und meist sind diese Fehler noch schwerer zu debuggen).

    Kannst du hier etwas konkreter werden (möglichst im Bezug auf beide Smart-Pointer)?



  • Bin halt kein Fan von auto_ptr.
    - Die copy-problematik mit STL containern
    - dass die sich gegenseitig die Gültigkeit wegschnappen (der Fehler ist mal wirklich schwer zu finden wenn plötzlich ein Pointer seine Gültigkeit verliert ohne dass man ihm irgendwas zugewiesen hat)
    - dann kann man ihn nicht auf NULL testen.
    .
    .
    .

    shared_pointer ist schon besser. Aber wenn man hier vergisst neue Kopien vom shared_pointer zu machen (und sie Anstelle vom Objekt macht), dann kommt das ganze reference counting durcheinander - auch das ist extrem schwer zu debuggen.

    Nicht das es mit normalen Pointern leichter ist. Aber wenn ich einen shared_pointer einsetzte dann geh ich halt davon aus, dass er tut was er soll. Sprich: ich such bei Fehler zuletzt beim reference counting mechanismus.



  • Danke für die Ausführungen.

    Es stimmt schon, natürlich kann man die Smart-Pointer falsch anwenden. Gerade std::auto_ptr kann mit seiner Move-Semantik am Anfang verwirrend sein. Aber das sind meiner Meinung nach vor allem Anfängerprobleme, die hauptsächlich aus Unkenntnis der Smart-Pointer und ihrer Besitzsemantik resultieren.

    Hingegen ist ein sauberes Aufräumen auch für fortgeschrittene Programmierer eine Herausforderung, wenn der Code komplexer wird. Beispiel: Implementiere den folgenden Code ohne Smart-Pointers, sodass er auch im Falle von Exceptions keine Memory Leaks aufweist.

    void Function()
    {
        scoped_ptr<A> a(new A);
        scoped_ptr<B> b(new B);
        scoped_ptr<C> c(new C);
    
       if (condition)
          return;
    
       if (otherCondition)
          throw std::runtime_error("!");
    }
    


  • Übrigens: Folgende Mängel von std::auto_ptr haben mich dazu veranlasst, selbst einen verschiebbaren Smart-Pointer zu schreiben:

    • Deklaration benötigt vollständig definierten Pointee-Typ. Das ist manchmal, besonders in Header-Dateien, eine unnötige Abhängigkeit.
    • Tatsächlich kompiliert der Code in meiner Implementierung jedoch auch mit unvollständigem Typ und ruft im Destruktor undefiniertes Verhalten hervor (Freigabe eines nicht zerstörten Objekts).
    • Wie von dir erwähnt keine Methode, um auf Gültigkeit zu prüfen. get() ist nicht dafür konzipiert.
    • Kein swap() .

Anmelden zum Antworten