Wann new verwenden und wann nicht?
-
cooky451 schrieb:
Wenn du das ohne Arrays schaffst, kriegst du nen Keks.
void foo() { foo(); } int main() { foo(); }
-
314159265358979 schrieb:
cooky451 schrieb:
Wenn du das ohne Arrays schaffst, kriegst du nen Keks.
void foo() { foo(); } int main() { foo(); }
$> g++ test.cc [b]-O3[/b] $> ./a.out
Nach ein paar Minuten:
$> top ... PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 24389 seppj 20 0 4024 324 248 R 100 [b]0.0 [/b] 2:08.02 a.out ...
Hervorhebung durch mich.
-
Dann schubs ihm halt noch einen Parameter dazu
-
314159265358979 schrieb:
Dann schubs ihm halt noch einen Parameter dazu
void foo(int) { foo(3); } int main() { foo(3); }
Immer noch nix, nur Endlosschleife. Du musst dich schon anstrengen für deinen Keks. Tipp: Der GCC ist recht gut im Optimieren von tail recursions, selbst wenn die Umformung nicht so offensichtlich ist.
-
std::string foo(int i) { std::string s(i); return s + foo(i + 1); } int main() { foo(0); }
So vielleicht?
-
314159265358979 schrieb:
std::string foo(int i) { std::string s(i); return s + foo(i + 1); } int main() { foo(0); }
So vielleicht?
Ich habe Zeile 3 mal zu
std::string s(i, ' ');
gemacht, damit es compilert.Mal schaun....
%&&îäÿûÿ5ÿ
%
#@&@@&!%%Éÿ
ÎÊÂÕÐÞYREE$T% #T%ÉÝÉ%ÝT$
...Arghhhh!!!
Das war Absicht, oder?
Ich hätte mal genauer schauen sollen, bevor ich das starte.
Jedenfalls immer noch kein Stack Overflow. Dafür aber den ganzen Heap vollgemüllt, und den Rechner in eine minutenlange Auslagerungsorgie geschickt, bis ich den Prozess abschießen konnte. Und danach nochmal ein paar Minuten zurücklagern.
-
Sorry, das war wirklich nicht meine Absicht. Ich hätte damit gerechnet, dass der Stack eher eingeht, als der Heap. Naja, muss ich auch mal eben testen, welche Auswirkung das so auf mein System hat.
-
So, hatte eigentlich selbst bei fast 3 GB am Swap kein Problem, weiterzuarbeiten. Musik lief auch weiter. OS X ist halt einfach super
Edit: Läuft sogar besser, als vorher
-
314159265358979 schrieb:
Sorry, das war wirklich nicht meine Absicht. Ich hätte damit gerechnet, dass der Stack eher eingeht, als der Heap. Naja, muss ich auch mal eben testen, welche Auswirkung das so auf mein System hat.
Ich habe es wie gesagt nicht bis zu bitteren Ende getrieben, sondern sobald ich es merkte abgebrochen. Ich kann ja mal abschätzen:
sizeof(string) ist 8 bei mir. Stackgröße ist 163840 kB. Ich mag jetzt nicht in den Assemblercode gucken, was der int macht, aber sagen wir mal nochmal 4 Byte pro Call. Das sind knapp 14 Millionen mögliche Calls. Für jeden Call bekommen wir einen String, der 1 Zeichen länger ist als der vorherige, je nachdem wie genau es läuft bekommen wir auch 2 Strings dieser Länge, aber dafür auch 2 Strings auf dem Stack, das hebt sich ungefähr weg. Jedenfalls sind das nach Gauß rund Calltiefe^2/2 Bytes auf dem Heap. Plus noch ein paar Byte Verwaltungsdaten, die ich mal vernachlässige. Mein Heimrechner ist nicht so üppig ausgestattet, insgesamt gut 13 GB stehen zur Verfügung. Das heißt, nach ein paar hundertausend Calls, geht mir der Heap aus und das Programm würde vermutlich mit einer passenden Exception enden, anstatt mit Stackoverflow. Und es ist auch noch gewaltig Platz nach oben, man bräuchte also wirklich ungewöhnlich viel Arbeitsspeicher um den Stack mit diesem Programm zu knacken.314159265358979 schrieb:
So, hatte eigentlich selbst bei fast 3 GB am Swap kein Problem, weiterzuarbeiten. Musik lief auch weiter. OS X ist halt einfach super
Bei mir lief auch alles weiter, außer einen kleinen Videoaussetzer als er anfing auszulagern, wodurch ich überhaupt erst das Problem bemerkte. Ärgerlich war, dass das Programm selbst nur sehr träge auf das CTRL+C reagierte (beziehungsweise: Es hat sicherlich sehr prompt reagiert, aber die Millionen Anfragen nach mehr RAM waren schon abgeschickt und wurden noch verarbeitet). Und der Scheduler hat mir dann anscheinend den Flashplayer abgeschossen, als der RAM am Ende war
.
-
Pi: Alle von dir geposteten Programme haben Undefined Behaviour:
N3290 §1.10.24 schrieb:
The implementation may assume that any thread will eventually do one of the following:
— terminate,
— make a call to a library I/O function,
— access or modify a volatile object, or
— perform a synchronization operation or an atomic operation.
[Note: This is intended to allow compiler transformations such as removal of empty loops, even when termination cannot be proven. — end note]
-
Die Sache mit dem Stack und Heap interessiert mich mehr.
In .NET (C#) beispielsweise ist es so, dass alle Referenztypen (somit auch Instanzen von Klassen) auf dem Heap angelegt werden und Wertetypen werden auf dem Stack angelegt werden (aus Performancegründen). Die klare Trennung existiert in C++ nicht; ich bin also in der Lage frei zu entscheiden, ob meine Instanz auf dem Stack oder Heap angelegt werden soll. Mal angesehen von der "Schönheit" des Codes – wann weiß ich wann ich Möglichkeit 1 oder Möglichkeit 2 (s. Codebeispiel auf der ersten Seite) bevorzugen soll?
-
inc7 schrieb:
wann weiß ich wann ich Möglichkeit 1 oder Möglichkeit 2 (s. Codebeispiel auf der ersten Seite) bevorzugen soll?
Generell ist der Stack vorzuziehen. Grund: die Allokation auf dem Heap kostet Performance, das Speichermanagement muss außerdem selbst gehandelt werden (auch wenn Smartpointer das erleichtern), weil C++ keinen GC hat. Zugriffe über (smart-)Pointer auf Heap-Objekte dauern auch länger als auf Stackobjekte.
ABER: ein Stack-Objekt hat nur Scope-Lebenszeit, soll heißen, wenn die Funktion oder der Block verlassen wird, wird das Objekt zerstört. Solltest du also Objekte erzeugen, die länger leben sollen als die aktuelle Funktion, kommst du am Heap nicht vorbei.
-
In C# können auch Wertetypen auf dem Heap sein, aber das ist ein Detail. Um die Lage in C++ beurteilen zu können, musst die Frage nach der Lifetime der Objekte stellen. Wenn du mit auto-Semantik leben kannst, dann solltest du sie verwenden. Wenn nicht, kannst du anfangen an new zu denken
-
pumuckl schrieb:
Zugriffe über (smart-)Pointer auf Heap-Objekte dauern auch länger als auf Stackobjekte.
Hmmm... Ob man das so als generelle Aussage stehen lassen kann? Was bremst denn da? Cache misses? Wenn es da jetzt nichts anderes gibt, würde ich diese Aussage wieder zurück ziehen und getrennt auf den Cache hinweisen (und dass Cache misses teuer sind).
-
Ich würd sagen: In erster Linie die zusätzliche Indirektion
-
dot schrieb:
Ich würd sagen: In erster Linie die zusätzliche Indirektion
Kannst du da näher beschreiben und was genau meinst du mit Indirektion (ich kenne das Wort nicht)?
-
Auf ein Objekt am Heap greifst du über einen Zeiger zu. D.h. du musst zuerst den Wert des Zeigers auslesen um die Adresse für den eigentlichen Zugriff auf das Objekt zu bekommen.
Auf ein Objekt am Stack kannst du dagegen direkt zugreifen.
-
dot schrieb:
Auf ein Objekt am Stack kannst du dagegen direkt zugreifen.
Wie soll das denn gehen? Der Stack liegt doch nicht in der CPU? Ok, wenn man sich erst den Pointer aus dem Stack holen muss um ihn zu dereferenzieren ist das eine Indirektion mehr, aber man kann ja schon öfter davon ausgehen, dass der Zeiger schon in einem Register liegt.
-
Eine x86 CPU hat ein eigenes Register für den Base Pointer auf den aktuellen Stackframe und unterstützt entsprechende Adressberechnungen direkt in Hardware...
-
cooky451 schrieb:
Ok, wenn man sich erst den Pointer aus dem Stack holen muss um ihn zu dereferenzieren ist das eine Indirektion mehr, aber man kann ja schon öfter davon ausgehen, dass der Zeiger schon in einem Register liegt.
Jepp. Aber erstens muss er da mal irgendwann reingeladen worden sein, und zweitens gibt es zumindest auf der x86-Architektur nicht so wahnsinnig viele allgemein verwendbare Register.