Speicherlecks durch return-Pointer-Funktionen?

Programmiersprachen, APIs, Bibliotheken, Open Source Engines, Debugging, Quellcode Fehler und alles was mit praktischer Programmierung zu tun hat.
Antworten
hundvdf
Beiträge: 28
Registriert: 03.10.2002, 13:48
Kontaktdaten:

Speicherlecks durch return-Pointer-Funktionen?

Beitrag von hundvdf »

Hallo,
ich habe ein kleines Problem bei der Verwendung von Pointern als Rückgabewerte. Und zwar habe ich mir z.B. eine Klasse Vektor geschrieben:

Code: Alles auswählen

class Vektor {
public:
    Vektor *operator+(Vektor *v);
private:
    float x,y,z;
}

Vektor *Vektor::operator+(Vektor *v) {
    Vektor *neu=new Vektor();
    neu->x=this->x+v->x;
    neu->y=this->y+v->y;
    neu->z=this->z+v->z;
    return neu;
}
und diesen kann ich jetzt ja z.B. so verwenden:

Code: Alles auswählen

Vektor *a=new Vektor();
Vektor *b=new Vektor();
Vektor *c=*a+b;
und alles funktioniert, ich muss hinterher dann eben den Speicherbereich für a, b und c wieder freigeben.

Verwende ich das allerdins so:

Code: Alles auswählen

Vektor *a=new Vektor();
Vektor *b=new Vektor();
Vektor *c=new Vektor();
Vektor *d=*(*a+b)+c;
dann würde ja einmal für den Aufruf (*a+b) Speicherplatz reserviert und dann für *()+c nochmals (den ich dann über delete d; wieder freigeben kann). Aber danach hab ich ja keine Chance den Speicher der bei der ersten Addition reserviert wurde wieder freizugeben, oder sehe ich das falsch?

Dieses Problem tritt ja immer dann auf, sobald ich in einer Funktion Speicher reserviere und den Pointer darauf zurückgebe, aber dieser nicht gespeichert wird. Oder hat da der Compiler eine Erkennung drin, dass dieser dann automatisch ein delete einbaut? Oder muss ich das Ganze anders handhaben, also darf ich generell keine Pointer zurückgeben, also wäre es besser wenn ich das Ergebnis in einem zusätzlichen Parameter speichern lasse?

Hoffe, ihr könnt mir zu etwas mehr Verständis verhelfen...
Danke
hundvdf
Benutzeravatar
B.G.Michi
Establishment
Beiträge: 163
Registriert: 07.03.2006, 20:38
Alter Benutzername: B.G.Michi
Kontaktdaten:

Re: Speicherlecks durch return-Pointer-Funktionen?

Beitrag von B.G.Michi »

Hat es einen tieferen Sinn, dass du diese Vektorklasse auf Pointern als Rückgabewerte aufbaust?
Edit: sry, ich hab grad gemerkt dass ich Mist verzehlt hab... in dem Fall solltest du das Ergebnis als normalen "Vektor" zurückgeben

Code: Alles auswählen

class Vektor
{
public:
    Vektor operator+(const Vektor&) const;

private:
    float x, y, z;
};

Vektor Vektor::operator+(const Vektor& _v) const
{
    Vektor r;
    r.x = x + _v.x;
    ...
    return r;
}

int main(void)
{
    Vektor a = Vektor();
    Vektor b = Vektor();
    Vektor c = a + b;
    Vektor d = (a + b) + c;
}
und nein, der Compiler gibt von dir reservierten Speicher nicht automatisch wieder frei :)
Benutzeravatar
Krishty
Establishment
Beiträge: 8268
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Speicherlecks durch return-Pointer-Funktionen?

Beitrag von Krishty »

In C++ benutzt man new um zu signalisieren, dass man die Lebenszeitverwaltung (und damit auch die Speicherverwaltung) einer Ressource selber in die Hand nehmen möchte (z.B. um bestimmte Besitzmuster zu realisieren) anstatt die Automatische zu nutzen. Um „gewöhnliche“ Objekte zu erzeugen und rumzuschieben benutzt du eine blanke Deklaration (also Vektor a; – bloß nicht Vektor a();, weil C++ scheiße ist); damit passieren keine Speicherlecks, du sparst dir hundert Indirektionen und sie ist um den Faktor tausend schneller.

Gruß, Ky
Zuletzt geändert von Krishty am 17.02.2011, 16:19, insgesamt 1-mal geändert.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Alexander Kornrumpf
Moderator
Beiträge: 2119
Registriert: 25.02.2009, 13:37

Re: Speicherlecks durch return-Pointer-Funktionen?

Beitrag von Alexander Kornrumpf »

(also Vektor a; – bloß nicht Vektor a();, weil C++ scheiße ist)
Huh?
Benutzeravatar
Krishty
Establishment
Beiträge: 8268
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Speicherlecks durch return-Pointer-Funktionen?

Beitrag von Krishty »

Most Vexing Parse. Wenn du Vektor a(); schreibst, gilt das nicht als Definition eines Vektor-Objekts a per Standardk’tor sondern als Deklaration einer Funktion a ohne Parameter, die ein Vektor-Objekt zurückgibt. Das sollte ganz früher mal die Compiler-Logik einfach halten. Totale Scheiße.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
eXile
Establishment
Beiträge: 1136
Registriert: 28.02.2009, 13:27

Re: Speicherlecks durch return-Pointer-Funktionen?

Beitrag von eXile »

hundvdf hat geschrieben:

Code: Alles auswählen

Vektor *a=new Vektor();
Vektor *b=new Vektor();
Vektor *c=new Vektor();
Vektor *d=*(*a+b)+c;
Das müsste man doch lösen können, indem man einfach für Vektor und Vektor* unterschiedliche Operatoren definiert? Die innere Addition ist vom Typ (Vektor, Vektor*), die äußere vom Typ (Vektor, Vektor). Aber die Vorredner haben schon recht, wenn sie RAII dir ans Herz legen. ;)
hundvdf
Beiträge: 28
Registriert: 03.10.2002, 13:48
Kontaktdaten:

Re: Speicherlecks durch return-Pointer-Funktionen?

Beitrag von hundvdf »

Danke schonmal für die Antworten.
Krishty hat geschrieben:Most Vexing Parse. Wenn du Vektor a(); schreibst, gilt das nicht als Definition eines Vektor-Objekts a per Standardk’tor sondern als Deklaration einer Funktion a ohne Parameter, die ein Vektor-Objekt zurückgibt.
Ja, das ist klar. Ich lege Variablen (von komplexen Datentypen) meistens mit Vektor *a=new Vektor(); an und das passt ja soweit auch. Meine Frage bezog sich aber auf die Rückgabewerte.
eXile hat geschrieben:Das müsste man doch lösen können, indem man einfach für Vektor und Vektor* unterschiedliche Operatoren definiert? Die innere Addition ist vom Typ (Vektor, Vektor*), die äußere vom Typ (Vektor, Vektor). Aber die Vorredner haben schon recht, wenn sie RAII dir ans Herz legen. ;)
Ja, lösen könnte man das bestimmt, vielleicht war mein Beispiel mit dem + Operator auch nicht das Beste... Wenn ich aber steng nach RAII programmieren wollen würde, dann dürfte ich doch keine Pointer zurückgeben. Wenn ich aber komplexe Werte als nicht-Pointer zurückgebe, dann wird doch davon eine Kopie erstellt (genauso wie bei Call-By-Value), oder etwa nicht?
Also konkret:

Code: Alles auswählen

Vektor add(Vektor a,Vektor b) {
Vektor c=a+b;
return c;
}

main() {
Vektor A;
Vektor B;
Vektor C=add(A,B);
}
dann würde doch in der add-Funktion die Summe berechnet und dann der Speicherbereich von c (also der berechnete Vektor) nach C kopiert werden, oder sehe ich das falsch? Das macht vielleicht bei einem Vektor mit 3 floats noch nicht viel aus, aber je komplexer die Klasse wird, desto länger würde das umkopieren doch dauern. Deshalb war meine Idee, nur einen Pointer zurückzugeben.
Wäre es für komplexere Klassen/Strukturen dann besser, einen zusätzlichen Parameter mitzugeben, in welchem das Ergebnis gespeichert wird? Also z.B:

Code: Alles auswählen

void add(Vektor a,Vektor b,Vektor *dest) {
*dest=a+b;
}

main() {
Vektor A;
Vektor B;
Vektor C;
add(A,B,&C);
}
(Wobei natürlich die Gültigkeit des übergebenen Zeigers überprüft werden müsste)

Danke und Grüße
hundvdf
Benutzeravatar
CodingCat
Establishment
Beiträge: 1857
Registriert: 02.03.2009, 21:25
Wohnort: Student @ KIT
Kontaktdaten:

Re: Speicherlecks durch return-Pointer-Funktionen?

Beitrag von CodingCat »

Die Variante mit new ist definitiv vielfach langsamer als die Variante, einen einfachen Vektor (NICHT Vektor*) zurückzugeben. Tatsächlich solltest du so selten wie möglich mit new angelegte Objekte einfach so als Zeiger zurückgeben, und auf keinen Fall permanent neue temporäre Objekte mittels new anlegen, um winzige Kopien zu "sparen".

Um sinnvoll Kopien einzusparen, solltest du dich auf jeden Fall mit Referenzen vertraut machen. Die korrekte Variante für dein Eingangsbeispiel wäre Michis Variante:
B.G.Michi hat geschrieben:

Code: Alles auswählen

class Vektor
{
public:
    Vektor operator+(const Vektor&) const;

private:
    float x, y, z;
};

Vektor Vektor::operator+(const Vektor& _v) const
{
    Vektor r;
    r.x = x + _v.x;
    ...
    return r;
}

int main(void)
{
    Vektor a = Vektor();
    Vektor b = Vektor();
    Vektor c = a + b;
    Vektor d = (a + b) + c;
}


Die konstante Referenz (const Vektor& _v) sorgt dafür, dass intern tatsächlich die Adresse weitergegeben wird, und nicht kopiert wird. Der einzige Aufwand, der bleibt, ist der Kopieraufwand bei der Rückgabe. Aber auch hier hast du Glück: es ist dem Compiler erlaubt, die Kopie wegzuoptimieren (siehe Return value optimization), d.h. mit jedem vernünftigen Compiler sollte hier kein Zusatzaufwand anfallen.

Wenn du auf Nummer sicher gehen willst, wäre die einzige gültige Alternative in der Tat das Übergeben eines nicht-konstanten Zeigers bzw. einer entsprechenden Referenz auf ein Ergebnisobjekt, das du aber auch dann auf keinen Fall andauernd mittels new anlegen solltest, wenn dir die Laufzeit wichtig ist.

Edit: Auf Krishtys Anmerkung hin ent"Heap"t.
alphanew.net (last updated 2011-07-02) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite
Benutzeravatar
Krishty
Establishment
Beiträge: 8268
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Speicherlecks durch return-Pointer-Funktionen?

Beitrag von Krishty »

hundvdf hat geschrieben:Ich lege Variablen (von komplexen Datentypen) meistens mit Vektor *a=new Vektor(); an und das passt ja soweit auch.
Ich wette, sobald da irgendwo eine Exception fliegt hast du mehr Speicherlecks als wenn du überall alles mit Zeigern zurückgeben würdest ;) So lange die Objekte nicht größer als ein paar hundert KiB sind, immer automatisch allokieren.
hundvdf hat geschrieben:Wenn ich aber steng nach RAII programmieren wollen würde, dann dürfte ich doch keine Pointer zurückgeben. Wenn ich aber komplexe Werte als nicht-Pointer zurückgebe, dann wird doch davon eine Kopie erstellt (genauso wie bei Call-By-Value), oder etwa nicht?
Genau. Das Bisschen Einbuße durch die zusätzliche Kopie steht aber, so lange es nicht in einer inneren Schleife geschieht, in keinem Verhältnis zu den Lecks, potentiellen Fehlern und der Unübersichtlichkeit, die du dir mit Zeigern einhandelst. Und sollte die Leistung mal wichtig werden, kannst du Move Semantics implementieren. Und du kannst in 95 % der Fälle, in denen du Zeiger benutzt, Referenzen benutzen – das spart dir tausend * und &s. Benutz überall RAII; und ob eine Funktionen einen Rückgabewert, einen Zeiger oder eine Referenz benutzt ist danach zu entscheiden, ob es im Kontext sinnvoll ist, und nicht danach, wie schnell es ist.

CodingCat hat geschrieben:auf dem Heap angelegte Objekte
Vermeide es, in der Nähe Unkundiger von Stapel- und Freispeicher zu sprechen. Das sind Implementierungsdetails, die sie nur falsch verstehen und die mit Recht unter einer Abstraktionsschicht begraben liegen (genau wie das meist vollkommen unangebrachte und massiv gefährliche Detail, dass Zeiger und Referenzen Adressen seien). Es ist auch scheißegal, was schneller und was langsamer ist. Das einzige, was zählt, ist: Wenn man es ohne new schreibt gelten automatische Lebenszeitregeln für den aktuellen Block und man muss sich um nichts kümmern außer vernünftiger Kopiersemantik, ansonsten lebt es so lange bis delete drauf aufgerufen wird.

Nachtrag: Danke, Cat – ich lasse meinen Rant dennoch weiter hier stehen, weil das auch viele andere Profis nicht kapiert haben.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
BeRsErKeR
Establishment
Beiträge: 689
Registriert: 27.04.2002, 22:01

Re: Speicherlecks durch return-Pointer-Funktionen?

Beitrag von BeRsErKeR »

Falls du Pointer aus Funktionen zurückgeben willst (was in deinem Beispiel wenig Sinn macht) dann nutze std::auto_ptr oder ähnliches:

Code: Alles auswählen

#include <memory>

std::auto_ptr<vector> my_func()
{
    return std::auto_ptr<vector>(new vector);
}

int main()
{
    std::auto_ptr<vector> x = my_func();
    my_func();

    return 0;
}
Das hat auch folgenden Vorteil: Wenn du den Rückgabewert mal nicht nutzt wie in der 2. Zeile der main-Funktion, wird der Speicher dennoch freigegeben. Und du musst dich allgemein nicht selbst um das Freigeben kümmern. Das ganze ist auch exception-safe.
Ohne Input kein Output.
hundvdf
Beiträge: 28
Registriert: 03.10.2002, 13:48
Kontaktdaten:

Re: Speicherlecks durch return-Pointer-Funktionen?

Beitrag von hundvdf »

CodingCat hat geschrieben:Die Variante mit new ist definitiv vielfach langsamer als die Variante, einen einfachen Vektor (NICHT Vektor*) zurückzugeben.
Oh, das wusste ich nicht, aber es stimmt, habs gerade mal schnell ausprobiert, die new-Methode dauert ungefähr 10 mal so lange. Aber warum ist das so? Was ist der interne Unterschied darin, ob ich ne Variable mit Vektor *a=new Vektor(); oder mit Vektor a; anlege? In beiden Fällen wird doch Speicher bereitgestellt und der Standardkonstruktor aufgerufen.
CodingCat hat geschrieben:Tatsächlich solltest du so selten wie möglich mit new angelegte Objekte einfach so als Zeiger zurückgeben, und auf keinen Fall permanent neue temporäre Objekte mittels new anlegen, um winzige Kopien zu "sparen".
gut, das wurde mir jetzt klar.
CodingCat hat geschrieben:Um sinnvoll Kopien einzusparen, solltest du dich auf jeden Fall mit Referenzen vertraut machen. Die korrekte Variante für dein Eingangsbeispiel wäre Michis Variante
Ja, das ist mir klar, das oben war auch nur schnell ein Beispiel. Dass es mit Call-By-Reference (oder Call-By-Pointer) die Werte nicht kopiert werden müssen und daher schneller sind als mit Call-By-Value.
CodingCat hat geschrieben:Der einzige Aufwand, der bleibt, ist der Kopieraufwand bei der Rückgabe. Aber auch hier hast du Glück: es ist dem Compiler erlaubt, die Kopie wegzuoptimieren, d.h. mit jedem vernünftigen Compiler sollte hier kein Zusatzaufwand anfallen.
Das ist natürlich gut, das wusste ich nicht (und hab das Beispiel von Wiki gerade mal getestet und es funktioniert sogar ;) ) und ohne das bliebe ja nur noch die Alternative mit dem Übergeben eines nicht-konstanten Zeigers wie du es ja auch schon geschrieben hast.
Krishty hat geschrieben:Ich wette, sobald da irgendwo eine Exception fliegt hast du mehr Speicherlecks als wenn du überall alles mit Zeigern zurückgeben würdest ;) So lange die Objekte nicht größer als ein paar hundert KiB sind, immer automatisch allokieren.
Oh, das stimmt natürlich, da hab ich gar nicht dran gedacht...
Krishty hat geschrieben:
CodingCat hat geschrieben:auf dem Heap angelegte Objekte
Vermeide es, in der Nähe Unkundiger von Stapel- und Freispeicher zu sprechen.
Danke, das war ein sehr gutes Stichwort... Ich hab ja eigentlich nicht umsonst ein Buch über die C++-Grundlagen bei mir herumliegen, da steht der Heap sehr schön erklärt drin... Und das beantwortet evtl. auch schon meine Frage von oben: der Stack an sich ist zwar langsamer als der Heap, aber die Speicheranforderung vom Stack ist wesentlich schneller, so dass sich der Heap nur bei wirklich großen Objekten lohnt. Richtig so?

Danke und Grüße
hundvdf

PS: Mein Hauptproblem ist wohl eher, dass ich schon seit Ewigkeiten kein C++ mehr Programmiert hab, sondern durch das Studium nur ein bisschen mit Java, und da muss man sowas ja nicht unbedingt beachten.
Benutzeravatar
Krishty
Establishment
Beiträge: 8268
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Speicherlecks durch return-Pointer-Funktionen?

Beitrag von Krishty »

hundvdf hat geschrieben:der Stack an sich ist zwar langsamer als der Heap, aber die Speicheranforderung vom Stack ist wesentlich schneller, so dass sich der Heap nur bei wirklich großen Objekten lohnt. Richtig so?
Nein, Stapel- und Freispeicher sind gleich schnell (der Stapel kann ein bisschen schneller sein weil er gegenüber freien Allokationen höhere Lokalität bietet; also weniger Cache-Verfehlungen). Allokieren kostet auf dem Stack quasi nichts, auf dem Freispeicher ist das unterschiedlich aber in den meisten Situationen locker tausend Mal langsamer weil das Betriebssystem erst einen neuen Speicherblock allokieren, reservieren und initialisieren muss.

Der Stapelspeicher erlaubt außerdem keine dynamischen Allokationen; jedenfalls nicht in C++.

Aber die Unterschiede sind in 99 % der Fälle scheißegal. Benutz new nur, wenn du eine Array-Größe erst zur Laufzeit kennst (häufig, dann übernimmt das aber std::vector für dich und den kannst du wiederum automatisiert speichern), den Besitz des Objekts selber regeln willst (selten) oder das Objekt außergewöhnlich groß ist (sizeof(), nicht die logische Größe!) (äußerst selten). Bei mehr als einem new auf tausend Zeilen hast du garantiert was falsch gemacht.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
hundvdf
Beiträge: 28
Registriert: 03.10.2002, 13:48
Kontaktdaten:

Re: Speicherlecks durch return-Pointer-Funktionen?

Beitrag von hundvdf »

Krishty hat geschrieben:
hundvdf hat geschrieben:der Stack an sich ist zwar langsamer als der Heap, aber die Speicheranforderung vom Stack ist wesentlich schneller, so dass sich der Heap nur bei wirklich großen Objekten lohnt. Richtig so?
Nein, Stapel- und Freispeicher sind gleich schnell (der Stapel kann ein bisschen schneller sein weil er gegenüber freien Allokationen höhere Lokalität bietet; also weniger Cache-Verfehlungen).
Hm, ich hab das vorhin irgendwo gelesen dass der Heap etwas schneller sein soll, finde das aber gerade nicht mehr. Aber meine Tests ergeben das gleiche Ergebnis:

Code: Alles auswählen

Vektor a;
Vektor *b=new Vektor();

Ctimer timer;
timer.start();
for(int i=0;i<100000000;i++) {
	a.setX(i%100);
}
cout << timer.stop() << endl;
timer.start();
for(int i=0;i<100000000;i++) {
	b->setX(i%100);
}
cout << timer.stop();
hierbei komme ich bei a auf 1,3 und bei b auf 1,1 Sekunden, was darauf deuten würde, dass die Variable auf dem Heap ca. 10% schneller ist, kann natürlich auch sein, dass der Compier da was optimiert hat. Aber wie du schon sagst, das sind wirklich minimale Geschwindigkeitsvorteile (zumal ich den Test ja mit 100 Mio Zugriffen gemacht habe) und im Allgemeinen egal, gegenüber dem Komfort von Referenzen (den ich jetzt entdeckt habe :) )
Krishty hat geschrieben:Bei mehr als einem new auf tausend Zeilen hast du garantiert was falsch gemacht.
Ja, das kommt ungefähr hin, jetzt hab ich außer bei throw new Exception() noch ein einziges new drin (ca. 1200 Codezeilen). Ich sag jetzt besser nicht, wie viele news ich in der letzten Stunde gelöscht habe...

Und nebenbei bemerkt läuft das Programm jetzt auch stabil (und tendenziell auch eine minimale Spur schneller).

Danke!
hundvdf
Benutzeravatar
Krishty
Establishment
Beiträge: 8268
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Speicherlecks durch return-Pointer-Funktionen?

Beitrag von Krishty »

hundvdf hat geschrieben:jetzt hab ich außer bei throw new Exception() noch ein einziges new drin
Auch throw kommt ohne new aus. Ist theoretisch schon allein deshalb empfehlenswert, weil auch new Exceptions schmeißen kann und der Standard für diesen Fall vorsieht, dass das Programm sofort beendet wird – solltest du also zufällig in die Situation kommen, keinen Speicher mehr zu haben, und würfest aufgrund dessen eine Ausnahme, und benutztest dazu new, dann würde dein Programm einfach so weg sein bevor das throw auch nur erreicht wäre. (Ganz abgesehen davon, dass throw new im Kompilat eine ganze Menge Logikaufwand bewirkt, die 1) überflüssig, 2) so gut wie nie genutzt und 3) schwer optimierbar ist.)

Und ja, moderne, sauber auf RAII ausgerichtete Programme sind schneller und kleiner als return-Codes und Zeigerschieberei.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
hundvdf
Beiträge: 28
Registriert: 03.10.2002, 13:48
Kontaktdaten:

Re: Speicherlecks durch return-Pointer-Funktionen?

Beitrag von hundvdf »

Krishty hat geschrieben:
hundvdf hat geschrieben:jetzt hab ich außer bei throw new Exception() noch ein einziges new drin
Auch throw kommt ohne new aus.
Ja, ich wollte damit auch nur sagen, dass ich die noch nicht wegprogrammiert habe, was ich auch noch vorhabe. Bei den Exception habe ich eben news genommen, weil ich das von Java so gewohnt war und darüber nicht wirklich nachgedacht habe... Wie gesagt, C++ ist bei mir schon lange her...

Grüße
hundvdf
Antworten