Was passiert genau, wenn ich einen Point mit NULL initialisiere?
-
asc schrieb:
Das würde ich so nicht stehen lassen, auch wenn man sich fragen sollte ob man sich selbst um die Speicherverwaltung kümmern muss.
Darauf wollte ich jetzt gar nicht hinaus. Mir ging es um die Zeit, die ein new/delete verbrät. Deswegen sollten man damit lieber sparsam umgehen. Objekte verschlimmern diese ganze Angelegenheit auch nur.
Das hängt aber auch stark von der Zielplattform ab. Auf einem normalen Desktop-Computer braucht man sich in der Regel keine Gedanken darum machen.
-
Paul Müller schrieb:
...gefährliches Halbwissen...
Tut mir leid, aber fast alles was du in deinen letzten beiden Beiträgen geschrieben hast ist Quatsch.
-
@SeppJ
Was den genau? Ich kann im letzten Beitrag von mir keinen Unsinn entdecken. Maximal eine Pauschalisierung bzgl. ob man auf Desktops ressourcensparend programmieren sollten. Wenn ich dich aber richtig verstehe, würdest du die Aussage sogar ausweiten, anstatt einschränken wollen?
-
Paul Müller schrieb:
@SeppJ
Was den genau? Ich kann im letzten Beitrag von mir keinen Unsinn entdecken. Maximal eine Pauschalisierung bzgl. ob man auf Desktops ressourcensparend programmieren sollten. Wenn ich dich aber richtig verstehe, würdest du die Aussage sogar ausweiten, anstatt einschränken wollen?Ok, hier eine detaillierte Analyse:
Mir ging es um die Zeit, die ein new/delete verbrät. Deswegen sollten man damit lieber sparsam umgehen.
Und was soll man stattdessen nehmen? Entweder braucht man Heapobjekte oder eben nicht. Grundlos new/delete benutzen meistens nur Leute die von Java kommen, weil sie es nicht anders kennen.
Objekte verschlimmern diese ganze Angelegenheit auch nur.
Unsinn. Abstraktion kostet keine Laufzeit.
SeppJ schrieb:
Idealerweise programmiert man so, dass solche Probleme gar nicht erst auftreten können.
Das ist meist schwierig, wenn es um zeitkritische Aufgaben geht.
Unsinn. Abstraktion kostet keine Laufzeit.
SeppJ schrieb:
allgemein: delete prüft ja ohnehin irgendwie, ob die Freigabe gültig ist.
Im einfachsten Fall würdest du dann eine Exception bekommen, diese müsste du aber wieder gesondert behandeln.
Was willst du mir damit sagen? Ich schildere nur den Sachverhalt. Es ging darum, ob das delete dadurch langsamer wird, weil es die 0 gesondert behandelt. Das wird es nicht wesentlich, weil beim delete sowieso irgendwelche Prüfungen durchgeführt werden.
Und wie schon angemerkt, was heißt gültig, ein Speicherbereich kann auch wieder vergeben werden.
Gültig heißt, dass es vorher ein passendes new gab. Speicher wird nicht wieder vergeben, bevor er nicht freigegeben wurde. Wenn du über mehrere Pointer auf den gleichen Speicherbereich zugreifst (so dass er zwischendurch freigegeben und wieder zugewiesen werden kann, ohne dass ein anderer Pointer es merkt), sind wir wieder im Bereich unsauberer Programmierung.
Von new und delete soll man ja auch die Finger lassen soweit es möglich ist.
Da haste Recht. Aber ich glaube die Gründe sind dir nicht so ganz klar.
-
SeppJ schrieb:
Und was soll man stattdessen nehmen? Entweder braucht man Heapobjekte oder eben nicht.
Man kann die Objekte auch einfach recyceln. Das man irgendwann mal ein Objekt erstellen muss steht dabei nicht zur Debatte.
SeppJ schrieb:
Unsinn. Abstraktion kostet keine Laufzeit.
Du willst mir nicht ernsthaft erzählen, dass ein ctor/dtor Aufruf genauso viel Laufzeit benötigt wie kein Aufruf?
SeppJ schrieb:
Unsinn. Abstraktion kostet keine Laufzeit.
Hier das gleiche. Du meinst nicht ernsthaft etwas zu machen geht genauso schnell, wie etwas nicht zu machen?
SeppJ schrieb:
Was willst du mir damit sagen? Ich schildere nur den Sachverhalt. Es ging darum, ob das delete dadurch langsamer wird, weil es die 0 gesondert behandelt. Das wird es nicht wesentlich, weil beim delete sowieso irgendwelche Prüfungen durchgeführt werden.
Meinetwegen werden Prüfungen durchgeführt. Diese belaufen sich aber auf NULL, der Trivialfall. Und ob der Zeiger mit new allokiert wurde. Andernfalls gibts eine Exception, die du fangen müsstest und irgendwas damit anstellen oder dein Programm kracht komplett weg.
Die mühselige Exception kann ich mir sparen, indem ich einfach den Pointer NULL setze. Und das hab ich zwar schon erwähnt aber ein delete zuviel tut nicht weh, nur ein delete zu wenig. Und wenn es nicht weh tut, ist es mir auch egal, ob man es als unsauber ansehen kann. Der Link von manni66 zeigt zwar, dass das nicht immer funktioniert, aber das kannst du ja in deinem Design berücksichtigen.SeppJ schrieb:
Gültig heißt, dass es vorher ein passendes new gab. Speicher wird nicht wieder vergeben, bevor er nicht freigegeben wurde.
Das hat auch niemand bestritten.
SeppJ schrieb:
Wenn du über mehrere Pointer auf den gleichen Speicherbereich zugreifst (so dass er zwischendurch freigegeben und wieder zugewiesen werden kann, ohne dass ein anderer Pointer es merkt), sind wir wieder im Bereich unsauberer Programmierung.
Siehe meine Anmerkung zu manni66 seinem Link.
SeppJ schrieb:
Da haste Recht. Aber ich glaube die Gründe sind dir nicht so ganz klar.
Ich denke schon. Ich finde es nur etwas merkwürdig das du bestreiten willst, dass ein Funktionsaufruf Laufzeit kostet. Ich denke die Bedeutung von inline-Funktionen ist dir vertraut?
-
Paul Müller schrieb:
SeppJ schrieb:
Unsinn. Abstraktion kostet keine Laufzeit.
Du willst mir nicht ernsthaft erzählen, dass ein ctor/dtor Aufruf genauso viel Laufzeit benötigt wie kein Aufruf?
Du willst mir nicht ernsthaft erzählen, dass da tatsächlich eine Funktion aufgerufen wird.
Die mühselige Exception kann ich mir sparen, indem ich einfach den Pointer NULL setze.
Die Exception und alles andere kannst du dir gleich sparen, indem du vernünftig programmierst und nicht irgendwelche Zeiger löscht, von denen du nicht einmal weißt, wohin sie zeigen.
Und das hab ich zwar schon erwähnt aber ein delete zuviel tut nicht weh, nur ein delete zu wenig.
Wenn du ein delete zu viel machst, dann hat dein Programm einen Fehler. An anderer Stelle. Und du merkst es noch nicht einmal, wenn du immer blind alles nullst.
SeppJ schrieb:
Da haste Recht. Aber ich glaube die Gründe sind dir nicht so ganz klar.
Ich denke schon. Ich finde es nur etwas merkwürdig das du bestreiten willst, dass ein Funktionsaufruf Laufzeit kostet. Ich denke die Bedeutung von inline-Funktionen ist dir vertraut?[/quote]Besser als dir offensichtlich.
-
SeppJ schrieb:
Du willst mir nicht ernsthaft erzählen, dass da tatsächlich eine Funktion aufgerufen wird.
Ich hoffe das soll ein Scherz sein. Selbst ein Anfänger würde nicht bestreiten wollen, dass ein geschriebener ctor eine Funktion ist. Aber selbst ein impliziter ctor oder einer mit nur einer Initialisierungsliste braucht Laufzeit. Auch wenn das lächerlich wenig ist, meinst du der Speicher füllt sich von allein?
SeppJ schrieb:
Die Exception und alles andere kannst du dir gleich sparen, indem du vernünftig programmierst und nicht irgendwelche Zeiger löscht,
Ich lösche nicht irgendwelche Zeiger. Das löschen ist auch wie schon mal angemerkt völlig uninteressant. Viel wichtiger ist es keine falschen Zeiger zu dereferenzieren. Und dass kann ich mit NULL auch viel besser machen, als wenn ich mir das Programm um die Ohren hauen lasse.
SeppJ schrieb:
von denen du nicht einmal weißt, wohin sie zeigen.
Was ja nur der Fall wäre, wenn ich sie nicht nullen würde. Siehe das Beispiel, wo der Zeiger oder besser die Adresse, schon wieder für eine Allokation verwendet wurde.
SeppJ schrieb:
Wenn du ein delete zu viel machst, dann hat dein Programm einen Fehler. An anderer Stelle. Und du merkst es noch nicht einmal, wenn du immer blind alles nullst.
Ich nulle nicht blind alles, sondern nur Zeiger die ich gelöscht habe. Somit merke ich dann in dem Fall schmerzlos, dass ich das Objekt zu früh gelöscht habe. Wenn ich einfach einen gültigen Zeiger NULL setze und damit die Verbindung verliere, um das Objekt zu löschen, dies wäre fatal, richtig.
-
Paul Müller schrieb:
SeppJ schrieb:
Du willst mir nicht ernsthaft erzählen, dass da tatsächlich eine Funktion aufgerufen wird.
Ich hoffe das soll ein Scherz sein. Selbst ein Anfänger würde nicht bestreiten wollen, dass ein geschriebener ctor eine Funktion ist. Aber selbst ein impliziter ctor oder einer mit nur einer Initialisierungsliste braucht Laufzeit. Auch wenn das lächerlich wenig ist, meinst du der Speicher füllt sich von allein?
Und der Anfänger läge damit falsch. Du auch. Seit wann ist Speicherfüllen ein Funktionsaufruf?
-
SeppJ schrieb:
Paul Müller schrieb:
SeppJ schrieb:
Du willst mir nicht ernsthaft erzählen, dass da tatsächlich eine Funktion aufgerufen wird.
Ich hoffe das soll ein Scherz sein. Selbst ein Anfänger würde nicht bestreiten wollen, dass ein geschriebener ctor eine Funktion ist. Aber selbst ein impliziter ctor oder einer mit nur einer Initialisierungsliste braucht Laufzeit. Auch wenn das lächerlich wenig ist, meinst du der Speicher füllt sich von allein?
Und der Anfänger läge damit falsch. Du auch. Seit wann ist Speicherfüllen ein Funktionsaufruf?
Also ihr sprecht da irgendwie über 2 verschiedene Sachen.
Initialisierung von Variablen kostet Zeit gegenüber dem Nichtinitialisieren. Ich glaube da sind wir uns einig.
Und ob der Konstruktor ein Funktionsaufruf ist oder nicht ist völlig plattformabhängig und wahrscheinlich auch davon was der Konstruktor im Endeffekt tut. Wenn es nur einen leeren Defaulkonstruktor gibt wird da wahrscheinlich nichts gemacht, sind aber mehrere verschiedene Konstruktoren vorhanden werden das sehr wahrscheinlich Funktionsaufrufe sein. Man müsste das halt mal überprüfen und ein wenig asm anschauen, aber ich kann mir das kaum anders vorstellen.
-
SeppJ hat vollkommen recht.
Denn wenn man im Ctor 3h lang initialisiert, so muss man das ohne Ctor ja auch tun, sonst wäre der Code nicht äquivalent. Wenn ich 500 Variablen füllen muss, muss ich das ob ich Ctors habe oder nicht.
Abstraktion kostet keine Performance in C++.
-
Ich glaube, es geht dem Paul um den Overhead für Funktionsaufrufe. Da sollte man wissen, dass sich Funktionsaufrufe inlinen lassen...
-
Damit du siehst, dass ich dir nicht bloß was erzähle:
Folgendes Programm:int main() { int data; cin>>data; cout<<data<<endl; }
gegen dieses:
class int_object { private: int data; public: friend ostream& operator<<(ostream &out, const int_object& in) { out<<in.data; return out; } friend istream& operator>>(istream &in, int_object& out) { in>>out.data; return in; } }; int main() { int_object data; cin>>data; cout<<data<<endl; }
Eines operiert direkt auf nativen Datentypen. Das andere operiert auf einem Klassenobjekt. Resultierender Maschinencode?
Dies für die erste Variante (gcc, -O3), relevanter Teil:.LFB1002: .cfi_startproc .cfi_personality 0x3,__gxx_personality_v0 pushq %rbp .cfi_def_cfa_offset 16 movl $_ZSt3cin, %edi pushq %rbx .cfi_def_cfa_offset 24 subq $24, %rsp .cfi_def_cfa_offset 48 leaq 12(%rsp), %rsi .cfi_offset 3, -24 .cfi_offset 6, -16 call _ZNSirsERi movl 12(%rsp), %esi movl $_ZSt4cout, %edi call _ZNSolsEi movq %rax, %rbx movq (%rax), %rax movq -24(%rax), %rax movq 240(%rbx,%rax), %rbp testq %rbp, %rbp je .L9 cmpb $0, 56(%rbp) je .L5 movzbl 67(%rbp), %eax .L6: movq %rbx, %rdi movsbl %al,%esi call _ZNSo3putEc movq %rax, %rdi call _ZNSo5flushEv addq $24, %rsp xorl %eax, %eax popq %rbx popq %rbp ret .p2align 4,,10 .p2align 3 .L5: movq %rbp, %rdi call _ZNKSt5ctypeIcE13_M_widen_initEv movq (%rbp), %rax movl $10, %esi movq %rbp, %rdi call *48(%rax) jmp .L6 .L9: .p2align 4,,5 call _ZSt16__throw_bad_castv .cfi_endproc
Dies für die zweite Variante:
.globl main .type main, @function main: .LFB999: .cfi_startproc .cfi_personality 0x3,__gxx_personality_v0 pushq %rbx .cfi_def_cfa_offset 16 movl $_ZSt3cin, %edi subq $16, %rsp .cfi_def_cfa_offset 32 movq %rsp, %rsi .cfi_offset 3, -16 call _ZNSirsERi movl (%rsp), %esi movl $_ZSt4cout, %edi call _ZNSolsEi movq _ZSt4cout(%rip), %rax movq -24(%rax), %rax movq _ZSt4cout+240(%rax), %rbx testq %rbx, %rbx je .L9 cmpb $0, 56(%rbx) je .L5 movzbl 67(%rbx), %eax .L6: movsbl %al,%esi movl $_ZSt4cout, %edi call _ZNSo3putEc movq %rax, %rdi call _ZNSo5flushEv xorl %eax, %eax addq $16, %rsp popq %rbx ret .p2align 4,,10 .p2align 3 .L5: movq %rbx, %rdi call _ZNKSt5ctypeIcE13_M_widen_initEv movq (%rbx), %rax movl $10, %esi movq %rbx, %rdi call *48(%rax) jmp .L6 .L9: .p2align 4,,5 call _ZSt16__throw_bad_castv .cfi_endproc
Unterschied der beiden? Ein paar andere Adressen und Registernamen. Und ein leaq wurde durch ein movq ersetzt.
-
SeppJ, um 100%ig zu überzeugen müsstest du beweisen, dass alle im Umlauf befindlichen C++-Compiler effektlose Funktionsaufrufe unter allen Umständen wegoptimieren.
Edit: Wobei ich mich da einfach drauf verlasse und mir lieber über andere Dinge den Kopf zerbreche...^^
Edit %2: Und in diesem fall auch, dass sie immer inlinen, wenn das ergebnis kürzer oder gleich dem Funktionsaufruf wäre.
-
Wie wär's denn mit folgender Aussage:
"Mit fast an Sicherheit grenzender Wahrscheinlichkeit kostet sinnvolle Abstraktion keine Performance ist mit absoluter Sicherheit in allen anderen Belangen überlegen".
-
Man müsste auch noch beweisen, dass nicht irgendein Compiler zum Spaß Warteschleifen im Code einbaut, um Leute zu bestrafen, die nicht mit Objekten programmieren. Laut Standard dürften sie das.
Und in diesem fall auch, dass sie immer inlinen, wenn das ergebnis kürzer oder gleich dem Funktionsaufruf wäre.
Die Sache, die Paul Müller wohl nicht ganz verstanden hat, ist, dass es da gar keinen Funktionsaufruf gibt. Man zeige mir den (meinetwegen inline) Konstruktor-/Destruktoraufruf. Wenn der Konstruktor nichts aktiv macht, dann gibt's auch keinen Code der generiert und ausgeführt werden könnte. Das ist genauso als würde man im ersten Programm die Zeile
int data;
im Maschinencode suchen. Auch die gibt es nicht, weil sie einfach nichts macht, sondern eine reine Abstraktion durch die Sprache ist (wenn auch auf sehr viel niedrigerem Abstraktionsniveau als die Klasse im zweiten Beispiel).Die Zeilen
int data;
undint_objekt data;
kommen nur insofern in den Maschinencode, als dass der Compiler sie benutzt, um die Adressen im Code richtig auszurechnen.
-
SeppJ schrieb:
Paul Müller schrieb:
Selbst ein Anfänger würde nicht bestreiten wollen, dass ein geschriebener ctor eine Funktion ist.
Und der Anfänger läge damit falsch. Du auch. Seit wann ist Speicherfüllen ein Funktionsaufruf?
Bitte alles lesen und nicht nur die ergänzenden Worte.
Shade Of Mine schrieb:
Denn wenn man im Ctor 3h lang initialisiert, so muss man das ohne Ctor ja auch tun, sonst wäre der Code nicht äquivalent.
Seit wann geht es bei Code-Abstraktion um äquivalenz? Klassen sind für die Wiederverwendbarkeit in der Regel recht allgemein gehalten.
Außerdem dreht sich die Frage nicht darum, ob ein Objekt anstatt dem reinen Code mehr Laufzeit kostet, sondern ob ein Objekt zusätzlich zum Code Laufzeit kostet?SeppJ schrieb:
ein Beispiel
Gut hast du also ein Beispiel gefunden, welches die Kriteren von Shade Of Mine erfüllt.
SeppJ schrieb:
Die Sache, die Paul Müller wohl nicht ganz verstanden hat, ist, dass es da gar keinen Funktionsaufruf gibt.
Ich weiß was inline macht. Du gehst aber die ganze Zeit davon aus, dass die Klasse exakt den selben Code ausführt, den du auch so verwendet hättest. Und das ist nicht immer der Fall. Nebenbei unterschlägst du auch, das inlining nicht immer möglich ist. Im Falle eines ctors/dtors zwar eigentlich schon, aber ein Objekt hat ja auch noch Methoden.
-
Paul Müller schrieb:
Außerdem dreht sich die Frage nicht darum, ob ein Objekt anstatt dem reinen Code mehr Laufzeit kostet, sondern ob ein Objekt zusätzlich zum Code Laufzeit kostet?
Und das tut es nicht. 0. Niemals.
Und die eigentliche Frage war ja, ob ein blanker Pointer schneller oder langsamer ist als ein sicher gekapselter Autopointer. Ich wollte auch eigentlich einen simplen Autopointer als Beispiel bringen, aber das wurde dann zu lang, weil ich für sauberen Code auch noch Kopierkonstruktor & Co hätte implementieren müssen und für Demonstrationszwecke diese auch noch benutzen.
Gut hast du also ein Beispiel gefunden, welches die Kriteren von Shade Of Mine erfüllt.
Auch beliebige andere Objekte. Code der das gleiche macht, braucht immer gleich lang, egal ob als Klasse, Template oder sonstwas.
SeppJ schrieb:
Die Sache, die Paul Müller wohl nicht ganz verstanden hat, ist, dass es da gar keinen Funktionsaufruf gibt.
Ich weiß was inline macht.
Da ist nichts zu inlinen. Da ist gar kein Funktionsaufruf. Zeig mir den geinlinten Konstruktoraufruf im Maschinencode, wenn da einer ist.
Du gehst aber die ganze Zeit davon aus, dass die Klasse exakt den selben Code ausführt, den du auch so verwendet hättest. Und das ist nicht immer der Fall.
Wieso nicht?
Nebenbei unterschlägst du auch, das inlining nicht immer möglich ist. Im Falle eines ctors/dtors zwar eigentlich schon, aber ein Objekt hat ja auch noch Methoden.
Wieso nicht?
Du hast es ja eigentlich sehr einfach, du brauchst bloß ein Gegenbeispiel zu zeigen, bei dem eine (natürlich nicht absichtlich schlecht programmierte) Klasse auch nur einen Maschinenbefehl mehr benötigt als ein vergleichbarer Code ohne Klasse. Dein Zug.
edit: Ok, hier ein Sparpointer:
int main() { int *data=new int; cin>>*data; cout<<*data<<'\n'; delete data; data=0; // Optional, aber du willst es ja so. Wird hier ohnehin wegoptimiert }
Gegen:
class heap_int { private: int *data; heap_int operator=(heap_int){return *this;} heap_int(const heap_int&){} public: heap_int():data(new int){} ~heap_int(){delete data;} int& operator*(){return *data;} }; int main() { heap_int data; cin>>*data; cout<<*data<<'\n'; }
Vorteil der Klasse: Nachdem die Entwicklungsarbeit für die Klasse erst einmal geleistet wurde (wurde sie ja bereits in der Standardbibliothek), ist der Code Anwendungscode kürzer und sicherer. Ich kann das new und das delete gar nicht vergessen und auch gar nicht doppelt aufrufen.
Resultierender Maschinencode ist wieder (für alle praktischen Zwecke) identisch:
pushq %rbx .cfi_def_cfa_offset 16 movl $4, %edi subq $16, %rsp .cfi_def_cfa_offset 32 .cfi_offset 3, -16 call _Znwm movl $_ZSt3cin, %edi movq %rax, %rbx movq %rax, %rsi call _ZNSirsERi movl (%rbx), %esi movl $_ZSt4cout, %edi call _ZNSolsEi leaq 15(%rsp), %rsi movl $1, %edx movq %rax, %rdi movb $10, 15(%rsp) call _ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_l movq %rbx, %rdi call _ZdlPv xorl %eax, %eax addq $16, %rsp popq %rbx ret
pushq %rbx .cfi_def_cfa_offset 16 movl $4, %edi subq $32, %rsp .cfi_def_cfa_offset 48 .LEHB0: .cfi_offset 3, -16 call _Znwm .LEHE0: movl $_ZSt3cin, %edi movq %rax, %rsi movq %rax, %rbx .LEHB1: call _ZNSirsERi movl (%rbx), %esi movl $_ZSt4cout, %edi call _ZNSolsEi leaq 31(%rsp), %rsi movl $1, %edx movq %rax, %rdi movb $10, 31(%rsp) call _ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_l .LEHE1: movq %rbx, %rdi call _ZdlPv xorl %eax, %eax addq $32, %rsp popq %rbx ret
An den Kommentaren die eingefügt wurden sieht man den wahren Unterschied: Der Compiler hat im zweiten Fall mehr gearbeitet. Ja, hier gibt es einen Unterschied. Dies ist der Preis der Sicherheit: Compilezeit.
-
SeppJ schrieb:
Du hast es ja eigentlich sehr einfach, du brauchst bloß ein Gegenbeispiel zu zeigen, bei dem eine (natürlich nicht absichtlich schlecht programmierte) Klasse auch nur einen Maschinenbefehl mehr benötigt als ein vergleichbarer Code ohne Klasse. Dein Zug.
Ich spar mir jetzt mal das Assembly, weil der Diff doch etwas länger ist.
int main() { int * foo(new int); int * bar(foo); delete bar; return 0; }
#include <tr1/memory> int main() { std::tr1::shared_ptr<int> foo(new int); std::tr1::shared_ptr<int> bar(foo); return 0; }
-
Die Code sind meiner Meinung nach nicht miteinander vergleichbar!
-
Das ist jetzt aber ein Vergleich von Äpfeln mit.. (nein Birnen wären noch zu nahe dran..) UFO's.
/EDIT
Wenn man für dein gewünschtes Verhalten einen solchen Smart Pointer nimmt, dann hat man auch nichts anderes als schlechte Performance verdient.Vergleichbar wäre so etwas:
class my_wrapper { public: my_wrapper(int* p) : p_(p){} my_wrapper(my_wrapper const& other) : p_(other.p_){} void destroy() { delete p_; } private: int* p_; }; int main() { my_wrapper foo(new int); my_wrapper bar(foo); bar.destroy(); return 0; }
Gleiche Vorteile, gleiche Nachteile. Das sollte jetzt so ziemlich zu dem asm Code kompiliert werden, wie deine Version.
Obwohl ich schon auch der Meinung bin, dass Abstraktion kosten kann. Kommt aber auf die Programmierweise und den Compiler an. Dass man sich über solche Sachen keine Gedanken machen muss, wenn man High Level programmiert ist ja selbstverständlich.