array über pointer returnen und ausgeben



  • Hi! Ich hab grad erst angefangen C++ zu lernen und hab mal eine Frage zu folgendem Code:

    #include <iostream>
    using namespace std;
    
    int *test();
    
    int main()
    {
      int *pointer ;
      pointer = test() ;
      cout<<*pointer++<<" "<<*pointer++<<" "<<*pointer++<<" "<<*pointer<<"\n" ;
    }
    
    int *test()
    {
      int array[4] = { 0, 1, 2, 3 } ;
      int *ptr ;
      ptr = array ;
      return ptr ;	
    }
    

    Eigentlich vollkommen verständlich nur die Ausgabe ist mir schleierhaft.
    Output:
    0 1 2 16384

    Wieso kommt da 16384 und nicht wie erwartet eine 3? Ich hab das Ganze dann auch mal mit einem 7 Stellen großen array probiert, also Werte von 0 bis 6. Da kam dann folgender Output:
    0 4481984 2 3 4 5 6

    Hier versteh ich wiederrum nicht wieso da 4481984 ausgegeben wird statt einer 1. Anschließend hab ich probiert die Werte des pointers in extra definierte Variablen zu übergeben. Das hat das erwartete Ergebnis geliefert. Nur es kann doch nicht der Sinn sein 7 Variablen zu definieren um die Zahlen auszugeben oder?

    Hoffe mir kann einer sagen wie ich das Problem löse und wieso da solche komischen Werte entstehen. Danke schonmal.



  • Du lieferst einen Zeiger auf eine lokale Variable zurück - das kann gut gehen, aber die Chancen stehen gut, daß der Wert längst überschrieben wurde, bevor du ihn zu Gesicht bekommst.



  • Heißt also das Array in der main methode definieren und dann einen Zeiger darauf an die Funktion übergeben und dann gehts? Alles klar danke. 🙂



  • Oder besser: Speicher "außen" organisieren - da, wo Du ihn auch benutzen willst....

    #include <iostream>
    #include <vector>
    
    using namespace std;
    
    void test(vector<int>& v);
    
    int main()
    {
      vector<int> myInts ;
      test(myInts);
      vector<int>::const_iterator pointer = myInts.begin();
    
      cout<<*pointer++<<" "<<*pointer++<<" "<<*pointer++<<" "<<*pointer<<"\n" ;
    }
    
    void test(vector<int>& v);
    {
      v.push_back(0);
      v.push_back(1);
      v.push_back(2);
      v.push_back(3);
    }
    

    Gruß,

    Simon2.



  • Simon2 schrieb:

    Oder besser: Speicher "außen" organisieren - da, wo Du ihn auch benutzen willst....

    #include <iostream>
    #include <vector>
    
    using namespace std;
    
    void test(vector<int>& v);
    
    int main()
    {
      vector<int> myInts ;
      test(myInts);
      vector<int>::const_iterator p = myInts.begin();
    
      cout<<*pointer++<<" "<<*pointer++<<" "<<*pointer++<<" "<<*pointer<<"\n" ;
    }
    
    void test(vector<int>& v);
    {
      v.push_back(0);
      v.push_back(1);
      v.push_back(2);
      v.push_back(3);
    }
    

    Gruß,

    Simon2.

    Wie gesagt hab grad erst angefangen. Wäre es möglich dazu ne Erkärung zu geben denn so an sich versteh ich das nicht.



  • Ach so .... sorry, hatte ich nicht beachtet.
    Ich gehe mal von Deinem Code aus - mit Arrays kennst Du Dich anscheinend schon ein wenig aus:
    A) Zwischenschritt: statisches Array "außen":

    #include <iostream>
    
    using namespace std;
    
    void test(int* v);
    
    int main()
    {
      int myInts[4]; // Platz für 4 ints schaffen
      test(myInts);
      int* pointer = &myInts[0]; // Adresse des ersten Elements; lt. Standard hätte auch "p = myInt;" gereicht
    
      cout<<*pointer++<<" "<<*pointer++<<" "<<*pointer++<<" "<<*pointer<<"\n" ;
    }
    
    void test(int* v);
    {
      v[0] = 0; v[1] = 1; // Füllen; jedes Element adressieren (mit []) und Wert zuweisen.
      v[2] = 2; v[3] = 3;
    }
    

    Nachteile:
    1.) Nirgends ist hinterlegt, wie viele Elemente in v passen; müsste man explizit übergeben. Ansonsten "knallts".
    2.) main() muss wissen, wieviele int's test() haben möchte

    😎 Zwischenschritt: vector (statt Array) mit vorgegebener Länge
    Vorteil: vector selbst hält seine Länge.

    #include <iostream>
    #include <vector>
    
    using namespace std;
    
    void test(vector<int>& v); // Übergabe per Referenz (&), damit wir nicht auf einer Kopie zu arbeiten
    
    int main() {
      vector<int> myInts(4); 
      test(myInts);
      vector<int>::const_iterator pointer = myInts.begin(); // Adresse des ersten Elements;
       // "Iteratoren" sind eine Verallgemeinerung von Pointern (Pointer sind "spezielle Iteratoren")
    
      cout<<*pointer++<<" "<<*pointer++<<" "<<*pointer++<<" "<<*pointer<<"\n" ;
    }
    
    void test(vector<int>& v);
    {
      if(v.size() < 4) return; // Vorteil: Hier kann ich die Länge überprüfen
      v[0] = 0; v[1] = 1; // wie oben: Man kann mit [] auch auf Vectorelementezugreifen
      v[2] = 2; v[3] = 3;
    
      // Alternativ kann ich statt v[0] auch v.at(0) nehmen und bekomme eine exception (s. Tutorial), 
      // wenn ich auf ein nicht vorhandenes Element zugreifen will.
    }
    

    Vorteil: Länge ist implizit dabei
    Nachteil: main() muss immer noch wissen, wieviele int's test() haben möchte.

    C) vector mit dynamischer Größe:
    Code: s.o.
    Hier verlängert test() selbst den vector mittels push_back() (=Methode von std::vector zu Anhängen eines Elements; der Anwender brauch sich nicht darum zu kümmern wie lang es werden soll und wie der vector sich seinen Speicher dafür besorgt/verwaltet).

    BTW1: Dass Du einem Vektor eine Anfangsgröß0e mitgibst (B) hindert Dich nicht daran, mittels push_back() später nochwas anzuhängen.
    BTW2: Die Ausgabe kannst Du auch viel einfacher machen:

    cout << myInts[0] << " " << myInts[1] << " "<< myInts[2] << " "<< myInts[3] << "\n";
    

    (tut's bei allen Varianten und macht "pointer" überflüssig)

    Gruß,

    Simon2.



  • Funktion test habe ich mal geändert. Aber den allokierten Speicher must
    du noch freigeben.
    Noch eine Anmerkung. So eine Programmierung sollte man sich nicht angewöhnen.

    int *test()
    {
      int array[4] = { 0, 1, 2, 3 } ;
      int *ptr = new int[4];
      ptr[0] = array[0] ;
      ptr[1] = array[1] ;
      ptr[2] = array[2] ;
      ptr[3] = array[3] ;
      return ptr ;   
    }
    


  • Simon2 schrieb:

    [...]

    Ok, jetzt wirds schon klarer. Danke! 🙂

    schokomann schrieb:

    Noch eine Anmerkung. So eine Programmierung sollte man sich nicht angewöhnen.

    "So eine Programmierung" heißt in dem Falle was? 😕



  • Der Aufrufer der Funktion test muss wissen, dass er den Speicher wieder
    freigeben muss. Besser ist die Verwendung der STL, wie es in den
    Beispielen schon gezeigt wurde, oder so ein C-Array in eine Klasse
    packen. Die Klasse kümmert sich dann um alles und stellt entsprechende
    Funktionen für den Zugriff zur Verfügung.
    Für ein Demoprogramm oder ein Beispiel was man machen kann und wie
    man es besser nicht machen sollte ist es o.k.
    Man kann an dem Bsp. zeigen welche Fehler man machen kann, worauf man
    bei solchem C-Code achten muss damit es knallt oder besser nicht.

    Bücher wie Effective C++, More Effective C++ (Scott Meyers),
    Modern C++ Design (Andrei Alexandrescu) u.s.w. solle man lesen
    und auch umsetzen. Angelika Langer (STL) hat ebenfalls gute Bücher
    herausgegeben.

    Das Geld sollte man ausgeben. Das macht sich später bezahlt.

    Ansonsten viel Spaß noch.



  • plis schrieb:

    ...

    schokomann schrieb:

    Noch eine Anmerkung. So eine Programmierung sollte man sich nicht angewöhnen.

    "So eine Programmierung" heißt in dem Falle was? 😕

    "Speicher (oder andere manuell freizugebende Ressourcen) anfordern und die Verantwortung für die Freigabe zu delegieren".

    Hier:
    - test() fordert Speicher an
    - main() muss das wissen und an der geeigneten Stelle freigeben.

    Das das ein ungünstiger Designansatz ist, kann man sehen, wenn man sich vorstellt, dass beide Funktionen von unterschiedlichen Programmierern implementiert werden. Vielleicht wird test() sogar nur als Lib angeliefert, deren Source man gar nicht zu Gesicht bekommt - woher soll der main()-Programmierer wissen, dass und wann er den Speicher wieder freigeben soll
    Ersteres schlägt z.B. fehl, wenn test() (vielleicht erst in der Version 1.1) folgendermaßen implementiert wurde:

    int *test()
    {
      static int array[4] = { 0, 1, 2, 3 } ; // mittels "static" überlebt das Objekt
      return array;
    }
    
    int main() {
       int* p = test();
       cout << p[1];
       delete[]; // RUMMS: delete auf static Speicher kommt nicht gut!
       return 0;
    }
    

    So ein "delete" führt in den Sumpf "undefinierten Verhaltens" (gerne mal ein segFault, aber theoretisch ist bin zum 3. Weltkrieg alles drin).

    Zweiteres kann z.B. in die Binsen gehen, wenn man soewas macht:

    int *test()
    {
       static int* array = 0;
       if(array == 0) array = new int[4]; // nur das Erste Mal anlegen.
       return array;
    }
    
    int main() {
       int* p, q;
       p = test();
       cout << p[1];
       delete[]; // geht in Ordnung, ...
    
       q = test(); // ... aber test() rechnet nicht damit
       cout << q[2]; // RUMMS - Zugriff auf bereits freigegebenen Speicher.
       return 0;
    }
    

    Auch hier: Undefiniertes Verhalten.

    In der Realität kommen noch sehr viel komplexere Situationen dazu, so dass sich insgesamt folgender Rat konsensfähig ist:

    schokomann schrieb:

    ...So eine Programmierung sollte man sich nicht angewöhnen....

    😃

    Gruß,

    Simon2.


Anmelden zum Antworten