Array an Funktion übergeben/zurückgeben c++

Velum

Grünschnabel
Hallo Community,
ich hoffe ihr könnt mir ein wenig unter die Arme greifen.
Bitte entschuldigt, wenn ich nicht im richtigen Forum gepostet habe, ich habe gelesen, c++ in Visual Studio ist nicht immer c++?!

Zu Beginn:
Ausgelesen aus einer Datei habe ich eine Tabelle und eine Spalte davon in ein Array gebracht. Jeder Wert des Arrays soll mit einem Wert multipliziert werden und in einem Neuen Array gespeichert werden. Das Ganze möchte ich als Funktion verpacken.

Mein Problem:
wie übergebe ich das Array der Funktion und wie bekomme ich wieder eins heraus?

Ansatz:
Funktionen sind mir als Anfänger geläufig, aber durch Google soll man wohl pointer benutzen wenn man bei Arrays bleiben möchte oder auf vectoren umsteigen. Letzteres kommt für mich nicht in Frage und mit pointern/zeigern tue ich mich noch schwer.

Auszug aus dem Programm, auf das Wesentliche gekürzt:
PHP:
using namespace std; //Übersichtlichkeitsgründe

//Prototypen
int *BerechnungPK(int As[], int anzahlBelegungen);
int main()
{
    const int Size = 4; // Für die größe des Arrays 
    int arr_Arbeitsstunden[] = { 23,4,9,13,51 }; // Beispiel Inhalt
   
    int *a = BerechnungPK(arr_Arbeitsstunden, Size);

    cout << "Personalkosten: " << a[3] << endl; //Beispielausgabe zum Testen
    system("pause");
    return 0;
}

//Personalkosten berechnen Funktion
int *BerechnungPK(int As[], int anzahlBelegungen)
{
    int Pkh = 32; //Personalkosten pro Stunde
    int ipk[] = { 0 };
    for (int i = 0; i < anzahlBelegungen; i++)
    {
        ipk[i] = As[i] * Pkh; //jeweilige Arbeitsstunden * Personalkosten
    }
    return ipk; //Berechneten Wert zurückgeben
}

Der Code wird erst einmal korrekt ausgeführt, bis er die Funktion erreicht und mir diesen Fehler ausgibt:
Run-Time Check Failure #2 - Stack around the variable 'ipk' was corrupted.

Ich hoffe ihr könnt mir einen Hinweis wo der Fehler liegt. Dass ich mich mit Pointern auseinander setzen muss ist mir aber klar.
 

Velum

Grünschnabel
Danke für die schnelle Antwort.
Code:
int ipk[] = new ipk[anzahlBelegungen];
Leider wird jetzt "new" und "ipk" als Fehler erkannt (rot unterkringelt)
Für das Aggregatobjekt wurde eine Initialisierung mit "{...}" erwartet.
Es wurde ein Typspezifizierer erwartet.

Außerdem, was macht "new" genau? Ist es richtig, dass ich zum Benutzen des Ausdruckes noch
Code:
#include <new>
hinzufügen muss ?
 

cwriter

Erfahrenes Mitglied
Bitte entschuldigt, wenn ich nicht im richtigen Forum gepostet habe, ich habe gelesen, c++ in Visual Studio ist nicht immer c++?!
C++ ist C++.
Microsoft hat aber gerne eigene Erweiterungen, was andere Compilerhersteller aber auch haben.
Microsoft hat aber zusätzlich C++/CX (und C++/CLI(?)), was .NET Aspekte in C++ einführt. Diese erkennt man i.d.R. an "ref" und '^'.
Dein Programm ist in C++, keine Angst.

Was vfl_freak sagte, ist korrekt.
Du musst zuerst Platz für den Array organisieren, dazu nutzt man "new".
Dein Code erstellt einen Array mit dem Element 0, also vom Typ "int[1]". Wenn du dann mit i > 0 darauf zugreifst, schreibst du in einen Bereich, der nicht definiert ist. Irgendwann merkt das die Runtime und gibt dir diesen Fehler, der genau das sagt: Du hast den Stackbereich in der Nähe des Arrays kaputt gemacht.

Ein delete sollte aber auch nicht fehlen...

Gruss
cwriter

/EDIT: Zum neuen Post: Es sollte "new int[anzahl]" sein.
 

sheel

I love Asm
(Sorry, langsam...)

Hi

keine Sorge, das ist C++

Vector würde die Sache jedenfalls vereinfachen...

...

Zuerst mal etwas dazu, wie Parameter an Funktionen übergeben werden:

Der Wert wird normalerweise kopiert (außer wenn man Referenzen verwendet, hier nicht der Fall). anzahlBelegungen in BerechnungPK zu ändern ändert zB. nur die Kopie und hat keine Auswirkungen auf den Wert im main. Diese Parameterkopien werden am Ende der Funktion wieder entfernt, so wie andere lokale Variablen (zB. Pkh) auch.

Großes Aber: Dass man sich ein "Array"-Variable als Pointer/Adresse zu den echten Werten vorstellen kann, weißt du ja (vermutlich). Wenn man jetzt arr_Arbeitsstunden an die Funktion übergibt wird nur die Speicheradresse kopiert, aber nicht die 5 Zahlen im Array. Sowohl die Originaladresse als auch die Kopieadresse zeigen zum selben Speicherbereich...

Deine Funktion könnte die ganze Sache mit ipk und return also lassen und die Werte von As direkt ändern. Fertig.

Btw., wenn man eine Arraykopie will, müsste man es zuerst im main kopieren (entweder ein fixes zweites Array anlegen und alle Werte per Schleife kopieren, oder falls die Arraygröße irgendwie variabel ist zusätzlich mit new/delete arbeiten) oder vector verwenden (wo das Kopieren auf die slebe Art schon reinprogrammiert ist)

...
Damit wäre die Funktion eigentlich fertig, trotzdem noch zu den anderen Problemen in der derzeitigen Form:

Zweites Thema: Arraygröße.
"Eigentlich" müsste man bei arr_Arbeitsstunden[] 5 in die Klammer schreiben, damit das Array 5 Elemente hat. Dort gehts aber auch so, weil danach eben schon 5 Werte kommen.
ipk wird als Array mit einem Element angelegt. Du greifst auf mehr zu ... ein Array wird nicht automatisch größer, => Fehler. Da die Größe anzahlBelegungen ist und theoretisch (für die Funktion gesehen) bei jedem Aufruf andere Werte haben kann, kann man auch keien fixe Größe in [] schreiben, sondern müsste mit new/delete arbeiten (oder vector verwenden, wo das Selbe schon fertig ausprogrammiert drin ist).

(Nicht-const-Variablen in [] als Größe angeben wird zwar von manchen Compilern erlaubt, ist aber dann nur eine eigene Erweiterung von C++, und in der Sprache C++ selber eigentlich nicht erlaubt).

...

Thema 3: Obwohl das Array arr_Arbeitsstunden beim Übergeben als Pointer behandelt wird, weil die Sprachregeln eben so sind, gibt es doch noch einen Unterschied zwischen einem Array wie arr_Arbeitsstunden und einem reinen Pointer: Einfach gesagt, ob der Inhalt dazugehört. Wenn die kopierte Speicheradresse in As am Funktionsende wieder gelöscht wird werden die 5 Werte vom Array nicht gelöscht. Wenn arr_Arbeitsstunden am main-Ende gelöscht wird, dann schon.

Weiters gilt die Sache mit dem Kopieren auch umgekehrt für Returnwerte (also bei Arrays/Pointern wird nur die Adresse kopiert).

Dabei ergibt sich mit ipk in der Funktion ein Problem: Das Array ipk ist eine lokale Variable der Funktion, wird also samt Inhalt am Funktionsende wieder gelöscht. Dass man grade eine Kopie der Adresse Richtung main rausgeschickt hat macht nichts: Der Speicher an der Adresse gehört dann nicht mehr zur Variable und wird evt. schon wieder für etwas anderes verwendet. Das cout draußen darf so also nicht passieren, die Werte sind schon weg.

Möglichkeit 1: ipk mit new anlegen, returnen, und im main nach Verwendung mit delete wieder löschen. Bei Arrays, die dynamisch mit new erzeugt werden und die erhaltene Adresse dann eben einem Pointer zugewiesen wird, ist dieser Pointer eben wirklich nur ein Pointer: Das Löschen davon löscht den Inhalt nicht mit. Umgekehrt ist man so eben selbst verantwortlich, delete[] irgendwo vor Programmende aufzurufen.
Und während diese Möglichkeit zwar funktioniert, ist es ziemlich unschön. Angefangen damit, dass manche Leute komplett gegen die Verwendung von new/delete sind, nur weil es eine Fehlermöglichkeit mehr gibt (delete vergessen) ... hier ist noch zusätzlich, dass new und delete in verschiedenen Funktionen sind. Jemand, der main liest, sieht das delete und kann dann suchen, in welcher der vorher aufgerufenen Funktionen das new dazu ist... (und umgekehrt).

Möglichkeit 2, wieder vector (wie oben geschrieben, das Kopieren inkl. Inhalt ist schon fertig reinprogrammiert)
 

sheel

I love Asm
Ergänzung: Nein, include<new> ist für new selber nicht nötig. Der Includename ist so gesehen etwas unpassend.
 

Velum

Grünschnabel
Danke cwriter und sheel.
(Sorry, langsam...)
keines Wegs! Ich bin ehrlich gesagt überrascht wie blitzartig und zudem noch ausführlich hier geantwortet wird. Leider fällt es mir schwer euch zu folgen. Dass die Array-Größe nicht definiert und deshalb der Fehler dabei raus kam war habe ich verstanden. Warum ich mir das return des Wertes ipk sparen kann ist mir unklar. Ich möchte den berechneten Wert aus der Funktion noch weiter verarbeiten.
"new" bereitet auch noch Probleme im Code.
Code:
int ipk[] = new int[anzahlBelegungen];

Wenn ich zudem noch
Code:
delete[] ipk;
ans Ende der main hängen soll ( ich hoffe den Teil habe ich so richtig verstanden) ist ipk in diesem Moment ja nicht mehr lokal definiert.


Da ich es ja hier auch schon mehrmals gelesen habe, wie könnte die Funktion und der Aufruf in der main mit vector aussehen ?
 

cwriter

Erfahrenes Mitglied
Warum ich mir das return des Wertes ipk sparen kann ist mir unklar. Ich möchte den berechneten Wert aus der Funktion noch weiter verarbeiten.
"new" bereitet auch noch Probleme im
Der Typ int[] ist in der Maschine äquivalent zu int*. (In der Sprache selbst gilt das nicht; ein int[] ist zwar ein int*, aber nicht umgekehrt).
Wenn du einen Array auf dem Stack erstellst (also bspw. int arr[42] ) dann bekommst du 42 ints an Stackspace, und "arr" zeigt auf das erste Element.
Als Parameter wird dann der Pointer übergeben, d.h. der Bereich, worauf er zeigt, bleibt gleich.
Nun ist der []-Operator Syntactic Sugar:
C:
arr[i] == *(arr + i)
D.h., wenn du schreibst, wird der Wert an der Adresse geändert, auf den arr zeigt, plus ein offset i (das mit der Grösse der Elemente, hier: int, multipliziert wird).
Daher kannst du, wenn du den Ausgangsarray nicht mehr brauchst, diesen direkt überschreiben.


ipk in diesem Moment ja nicht mehr lokal definiert.
Das ist der Witz an new und delete: Das Betriebssystem gibt dir Speicher. ipk ist nur lokal definiert, ja. Aber du gibst ja die Kopie an den Caller (das macht return), d.h. der Wert ist erhalten.
D.h. bei dir: Nutze delete[] a;
Denn a zeigt ja auf den Speicherbereich, den du mit new bekommen hast.
New erstellt Elemente auf dem Heap, ein normaler Array int[n] ist auf dem Stack.
Stackarrays werden gelöscht, wenn der Scope verlassen wird, Heap-Arrays nicht.
Schon von Memory leaks gehört? Das passiert genau dann, wenn delete vergessen wird: Ein Programm braucht immer mehr Speicher (mit new), gibt aber keinen frei (es geht nicht automatisch).
Stackleaks gibt es nicht wirklich, da sind Overflows / Corruptions ganz nett.
C++ hat Smartpointer, die das Risiko eines Leaks verringern.

Nun, ein Vektor wäre vermutlich Overkill hier.
Als return-Value ist ein Vektor schlecht, da teuer (Copy). Ab und zu geht es vielleicht als Move-Constructor, aber rohe Arrays (oder solche in Smartpointern) sollten hier gut gehen.

Siehe std::shared_ptr, falls du ein Return nutzen willst.

Gruss
cwriter
 

Velum

Grünschnabel
uff.... da sind so viele neue Begriffe im unteren Teil deines Beitrages cwriter, die muss ich noch mal nachlesen. Das Thema Pointer zusammen mit arrays ist jedenfalls etwas deutlicher geworden.


Vielleicht noch mal zum Syntax von new.
So sollte es ja aussehen: new «Datentyp»[«Menge»];
Anscheindend gebe ich hier aber noch was falsch ein?
Code:
//Prototypen
int *BerechnungPK(int As[], int anzahlBelegungen);
int main()
{
    const int Size = 4;
    int arr_Arbeitsstunden[] = { 23,4,9,13,51 };
   
    int *a = BerechnungPK(arr_Arbeitsstunden, Size);
    delete[] ipk;                                        //FEHLER bei ipk
   
    system("pause");
    return 0;
}

//Personalkosten berechnen Funktion
int *BerechnungPK(int As[], int anzahlBelegungen)
{
    int Pkh = 32;
    int ipk[] = new int[anzahlBelegungen];                // Fehler bei new

    for (int i = 0; i < anzahlBelegungen; i++)
    {
        ipk[i] = As[i] * Pkh;
    }
    return ipk;
}