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 Memory Management.
→ std.alloc · Fehlersuche · Rohspeicher
| 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.
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:
Create einen Puffer holt, gibt ihn im Destroy zurück. dispose ruft Destroy.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 …).EnvLookupRaw etwa zeigt in die Umgebung; ein free darauf zerstört fremden Speicher.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.
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 die Absturzstelle über ''%%--%%map-file'' — oder schlicht: die Schleife halbieren, bis das Wachstum verschwindet.
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„.alloc(n * 8) schreibt, braucht dasselbe n beim free — am besten als Feld daneben.0 setzen. Ein doppeltes free ist schlimmer als ein Leck.out_buf nicht.dispose statt free bei Objekten. Nur dispose ruft Destroy.@flight_crit sind alloc, new und lokale Felder verboten — allerdings nur im eigenen Rumpf (#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.