====== 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.