„Why should C++ programmers minimize use of 'new'?“: Die Kultfrage im Detail
Wenn Sie jemals auf Stack Overflow nach Antworten zu C++ gesucht haben, ist Ihnen diese Frage sicherlich begegnet. Sie hat tausende Stimmen und zehntausende Aufrufe erhalten. Und das zu Recht. Für Anfänger erscheint das Thema kontraintuitiv: „Wie soll das sein? In Java oder C# schreibe ich ständig new, und das ist normal. Warum ist das in C++ plötzlich schlecht?“
Heute übersetzen wir nicht einfach die Antwort aus dem englischsprachigen Forum. Wir werden es im Detail aufschlüsseln, warum die Minimierung von new in C++ keine Marotte von Gurus ist, sondern eine Frage der Leistung, Sicherheit und des gesunden Menschenverstandes. Und ja, wir werden Code schreiben, uns (fast) den Assembler ansehen und Nanosekunden zählen.
Wohin der Speicher geht: Stack vs. Heap
Bevor wir über new sprechen, erinnern wir uns an die Grundlagen. In C++ gibt es zwei Hauptorte zum Speichern von Daten:
- Stack – klein, schnell, wird automatisch verwaltet. Sobald eine Variable den Gültigkeitsbereich verlässt, wird sie zerstört. Sofort.
- Heap – groß, langsam, erfordert manuelle Verwaltung. Um dort Speicher zu erhalten, muss
new(odermalloc) aufgerufen werden. Um ihn freizugeben –delete.
Jeder Aufruf von new ist nicht einfach „gib mir Speicher“. Es ist ein komplexer Systemaufruf, die Suche nach einem freien Block, die Aktualisierung von Verwaltungsstrukturen. Es ist hunderte Male langsamer als die Zuweisung auf dem Stack.
Beispiel Nr. 1: Geschwindigkeitsvergleich
#include <chrono>#include <iostream>#include <vector>
struct Point { double x, y, z;};
int main() { const int N = 1000000;
// Stack-Zuweisung (Array auf dem Stack) auto start = std::chrono::high_resolution_clock::now(); Point points_stack[1000]; // 1000 Objekte auf dem Stack auto end = std::chrono::high_resolution_clock::now(); std::cout << "Stack time: " << std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count() << " ns";
// Dynamische Zuweisung über new start = std::chrono::high_resolution_clock::now(); std::vector<Point*> points_heap; for (int i = 0; i < 1000; ++i) { points_heap.push_back(new Point()); } end = std::chrono::high_resolution_clock::now(); std::cout << "Heap (new) time: " << std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count() << " ns";
// Aufräumen (unbedingt!) for (auto p : points_heap) delete p;
return 0;}Ergebnis (auf einer typischen Maschine):
Stack time: 0 nsHeap (new) time: 45200 nsDer Unterschied beträgt Zehntausende Male. Natürlich ist das synthetisch, aber es zeigt deutlich: Jedes überflüssige new kostet Mikrosekunden aus Ihrem Leistungsbudget.
Das Hauptproblem von new: Lecks und Fragmentierung
Wenn es nur um die Geschwindigkeit ginge, wäre das halb so schlimm. Aber new bringt eine ganze Reihe von Problemen mit sich:
1. Speicherlecks (memory leaks)
Haben Sie vergessen, delete aufzurufen? Herzlichen Glückwunsch, Sie haben ein Leck. Das Programm läuft eine Stunde, einen Tag, eine Woche – und stürzt plötzlich mit std::bad_alloc ab. Ein Klassiker. Besonders gefährlich sind Lecks in langlebigen Prozessen (Server, Spiele, GUI).
2. Hängende Zeiger (dangling pointers)
Sie haben das Objekt gelöscht, aber der Zeiger bleibt bestehen. Jeder Zugriff darauf ist undefiniertes Verhalten (UB). Das Programm kann funktionieren, abstürzen oder Ihre Musik durch Techno ersetzen. UB ist böse.
3. Heap-Fragmentierung
Häufige new/delete-Aufrufe zerteilen den Heap in kleine Stücke. Selbst wenn genug Speicher vorhanden ist, wird es unmöglich, einen großen Block zuzuweisen. Dies wird als externe Fragmentierung bezeichnet.