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 Memory Management.
→ std.alloc · Fehlersuche · 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
Createeinen Puffer holt, gibt ihn imDestroyzurück.disposeruftDestroy. - 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.
EnvLookupRawetwa zeigt in die Umgebung; einfreedarauf 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 die Absturzstelle über ''%%--%%map-file'' — oder schlicht: die Schleife halbieren, bis das Wachstum verschwindet.
Gewohnheiten, die Ärger ersparen
- Nach jedem
allocauf0prüfen.malloc_orpanicnimmt die Prüfung ab, wenn ein Fehlschlag ohnehin das Ende bedeutet. allocZeroedstattalloc, 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 dasselbenbeimfree— am besten als Feld daneben. - Zeiger nach dem Freigeben auf
0setzen. Ein doppeltesfreeist 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_bufnicht. disposestattfreebei Objekten. NurdisposeruftDestroy.
Wo Lyx mitprüft
- Unter
@flight_critsindalloc,newund lokale Felder verboten — allerdings nur im eigenen Rumpf (#1701). –static-analysisnennt 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.
