====== Speicher in der Praxis ====== Lyx hat keinen Garbage Collector und keine Referenzzählung. Wer anlegt, gibt frei — dafür ist die Laufzeit vorhersagbar. Diese Seite zeigt die Gewohnheiten, mit denen das im Alltag funktioniert; die Mittel selbst stehen in [[lyx_-_programmiersprache:sprache:memory-management|Memory Management]]. → [[lyx_-_programmiersprache:units:alloc|std.alloc]] · [[lyx_-_programmiersprache:guides:fehlersuche|Fehlersuche]] · [[lyx_-_programmiersprache:sprache:rohspeicher|Rohspeicher]] ---- ===== Drei Wege, Speicher zu bekommen ===== ^ Weg ^ Freigabe ^ Wofür ^ | **Stapel** — ''var x: int64'', ''%%[N]T%%'', ''struct'' | automatisch am Blockende | Alles, was klein ist und nicht überlebt | | **Heap roh** — ''alloc(n)'', ''allocZeroed(n)'' | ''free(p, n)'' | Puffer, deren Größe erst zur Laufzeit feststeht | | **Objekt** — ''new TKlasse(…)'' | ''dispose'' | Alles mit Zustand und Methoden | Dazu die libc-nahen Namen aus ''std.alloc'': ''malloc''/''calloc''/''realloc_mem''/''free_mem'' (ohne Größenangabe beim Freigeben), ''malloc_safe'' und ''malloc_orpanic'' (bricht ab, statt ''0'' zu liefern). **''free'' braucht dieselbe Größe wie die Anforderung.** Das ist der Preis dafür, dass kein Kopf vor dem Block liegt — und der häufigste Fehler beim Umstieg von C. ---- ===== Wer besitzt was ===== Die Frage, die jeden Speicherfehler entscheidet: **Wem gehört dieser Zeiger?** Lyx beantwortet sie nicht für dich, deshalb gehört sie in den Quelltext — als Konvention, nicht als Kommentar. Drei Muster reichen für fast alles: - **Der Anleger gibt frei.** Eine Klasse, die im ''Create'' einen Puffer holt, gibt ihn im ''Destroy'' zurück. ''dispose'' ruft ''Destroy''. - **Der Aufrufer stellt den Puffer** (''out_buf''). Die Funktion schreibt hinein und gibt eine Länge oder einen Fehlercode zurück — sie legt nichts an und gibt nichts frei. So arbeitet die halbe Standardbibliothek (''units/validate/*'', ''std.env'', ''InputBox'' …). - **Geliehen, nicht besessen.** Ein Zeiger, den eine Funktion nur liest, wird nicht freigegeben. ''EnvLookupRaw'' etwa zeigt **in** die Umgebung; ein ''free'' darauf zerstört fremden Speicher. ---- ===== Beispiel ===== Alle drei Muster in einem Programm: import std.io; import std.alloc; import std.string; pub type TListe = class { Daten: int64; // Puffer, uns gehoerend Kapazitaet: int64; Anzahl: int64; fn Create(kap: int64): void { self.Kapazitaet := kap; self.Anzahl := 0; self.Daten := allocZeroed(kap * 8); // genullt: kein Muell im Puffer } // Wer anlegt, gibt frei — Destroy raeumt den eigenen Puffer auf fn Destroy(): void { if (self.Daten != 0) { free(self.Daten, self.Kapazitaet * 8); self.Daten := 0; } } fn Anhaengen(w: int64): bool { if (self.Anzahl >= self.Kapazitaet) { return false; } poke64(self.Daten + self.Anzahl * 8, w); self.Anzahl := self.Anzahl + 1; return true; } fn Bei(i: int64): int64 { if (i < 0 || i >= self.Anzahl) { return 0; } return peek64(self.Daten + i * 8); } } // Ein Puffer, den der AUFRUFER stellt: die Funktion legt nichts an und // gibt nichts frei. Genau das Muster der Standardbibliothek. fn SchreibeNamen(out_buf: int64, max: int64): int64 { var s: pchar := "Lyx"; var n: int64 := StrLen(s); if (n >= max) { return 0 - 1; } var i: int64 := 0; while (i < n) { poke8(out_buf + i, StrCharAt(s, i)); i := i + 1; } poke8(out_buf + n, 0); return n; } fn main(): int64 { // 1. Heapblock von Hand var p: int64 := alloc(64); if (p == 0) { PrintLn("kein Speicher"); return 1; } poke64(p, 42); PrintLn(IntToStr(peek64(p))); free(p, 64); // Groesse muss zur Anforderung passen // 2. Objekt: new gehoert dispose var l: TListe := new TListe(4); l.Anhaengen(10); l.Anhaengen(20); PrintLn(IntToStr(l.Bei(0) + l.Bei(1))); dispose l; // ruft Destroy und gibt das Objekt frei // 3. Puffer des Aufrufers var buf: int64 := alloc(32); var n: int64 := SchreibeNamen(buf, 32); Print("Name: "); Print(buf as pchar); Print(" ("); Print(IntToStr(n)); PrintLn(" Zeichen)"); free(buf, 32); return 0; } 42 30 Name: Lyx (3 Zeichen) Übersetzt und ausgeführt mit ''lyxc 1.1.3I''. ---- ===== Lecks finden ===== Es gibt keinen eingebauten Leckprüfer — aber ein Leck ist messbar. Zwei Programme, die dasselbe 200 000-mal tun, einmal mit und einmal ohne ''free'': /usr/bin/time -v ./leak 2>&1 | grep "Maximum resident" /usr/bin/time -v ./noleak 2>&1 | grep "Maximum resident" Maximum resident set size (kbytes): 199936 # ohne free Maximum resident set size (kbytes): 256 # mit free Ein Faktor 780 — nachgemessen mit ''lyxc 1.1.3I''. Das Verfahren taugt für jede Schleife, die im Verdacht steht: **gleiche Arbeit oft genug wiederholen und den Speicherverbrauch ansehen.** Wächst er linear mit der Zahl der Durchläufe, fehlt eine Freigabe. Für die Zuordnung zur Stelle hilft danach [[lyx_-_programmiersprache:guides:fehlersuche|die Absturzstelle über ''%%--%%map-file'']] — oder schlicht: die Schleife halbieren, bis das Wachstum verschwindet. ---- ===== Gewohnheiten, die Ärger ersparen ===== * **Nach jedem ''alloc'' auf ''0'' prüfen.** ''malloc_orpanic'' nimmt die Prüfung ab, wenn ein Fehlschlag ohnehin das Ende bedeutet. * **''allocZeroed'' statt ''alloc''**, wenn der Puffer gelesen wird, bevor alles beschrieben ist. Uninitialisierter Heap ist der Grund für „manchmal geht es". * **Größe an einer Stelle halten.** Wer ''alloc(n * 8)'' schreibt, braucht dasselbe ''n'' beim ''free'' — am besten als Feld daneben. * **Zeiger nach dem Freigeben auf ''0'' setzen.** Ein doppeltes ''free'' ist schlimmer als ein Leck. * **Puffer nicht zurückgeben, sondern füllen lassen.** Eine Funktion, die eine Zeichenkette zurückgibt, muss sagen, wer sie freigibt — eine Funktion mit ''out_buf'' nicht. * **''dispose'' statt ''free'' bei Objekten.** Nur ''dispose'' ruft ''Destroy''. ---- ===== Wo Lyx mitprüft ===== * Eine Variable vom Klassentyp **ohne** ''new'' oder ''null'' wird abgewiesen ([[https://github.com/SEOLizer/LyX-Compiler/issues/1519|#1519]], auch über Unit-Grenzen [[https://github.com/SEOLizer/LyX-Compiler/issues/1570|#1570]]). * Unter ''@flight_crit'' sind ''alloc'', ''new'' und lokale Felder verboten — allerdings nur im eigenen Rumpf ([[https://github.com/SEOLizer/LyX-Compiler/issues/1701|#1701]]). * ''--static-analysis'' nennt den Stapelbedarf je Funktion; ''@stack_limit(N)'' macht daraus eine Zusage. Letzte Aktualisierung: 2026-08-19 — Beispiel und Leckmessung mit ''lyxc 1.1.3I'' nachgemessen; API gegen ''std/alloc.lyx'' erhoben.