====== Rohspeicher: alloc, peek & poke ======
Die gesamte Lyx-Standardbibliothek arbeitet auf einer einzigen Speicherabstraktion: ''alloc(n)'' liefert einen rohen Byte-Puffer als ''int64''-Adresse, ''peek8''/''poke8'' lesen und schreiben einzelne Bytes, ''peek64''/''poke64'' lesen und schreiben 8-Byte-Wörter. Kein GC, keine versteckten Allokationen, kein Overhead.
Diese Seite erklärt das Muster und zeigt, wie es in der Praxis eingesetzt wird. Sie ergänzt die allgemeine Seite [[lyx_-_programmiersprache:sprache:memory-management|Memory Management]] um die Low-Level-Schicht, auf der die Standardbibliothek aufbaut.
→ [[lyx_-_programmiersprache:units:alloc|std.alloc]] · [[lyx_-_programmiersprache:sprache:handles-und-fd|Handle-Konzept]] · [[lyx_-_programmiersprache:sprache:std-fehlerkonventionen|Fehler-Konventionen]]
----
===== alloc und free =====
''std.alloc'' stellt zwei Grundoperationen bereit:
^ Funktion ^ Signatur ^ Beschreibung ^
| ''alloc'' | ''alloc(n: int64): int64'' | Allokiert ''n'' Bytes. Gibt die Startadresse zurück, 0 bei OOM. Inhalt undefiniert (nicht nullisiert). |
| ''free'' | ''free(ptr: int64, n: int64): void'' | Gibt ''n'' Bytes ab ''ptr'' frei. **Beide Parameter** sind Pflicht — Lyx hat keine versteckte Größeninformation. |
import std.alloc;
fn CreateBuffer(size: int64): int64 {
var buf: int64 := alloc(size);
if (buf == 0) { return 0; } // OOM
return buf;
}
fn DestroyBuffer(buf: int64, size: int64): void {
free(buf, size);
}
> Die Größe bei ''free'' muss exakt der bei ''alloc'' übergebenen Größe entsprechen. Das ist kein Versehen — es ermöglicht dem Allocator, ohne Metadaten-Overhead zu arbeiten.
----
===== peek und poke — Roher Speicherzugriff =====
Lyx liest und schreibt Speicher byte-genau über vier Primitive:
^ Funktion ^ Liest/Schreibt ^ Beschreibung ^
| ''peek8(addr)'' | 1 Byte | Liest uint8 an Adresse ''addr'' |
| ''poke8(addr, v)'' | 1 Byte | Schreibt uint8 ''v'' an Adresse ''addr'' |
| ''peek64(addr)'' | 8 Bytes | Liest int64 little-endian an Adresse ''addr'' |
| ''poke64(addr, v)'' | 8 Bytes | Schreibt int64 ''v'' little-endian an Adresse ''addr'' |
Diese Primitiven sind **in der Sprache eingebaut** — kein Import nötig. Sie sind das Fundament für alle Struct-ähnlichen Datenstrukturen in der Standardbibliothek.
----
===== Structs als Byte-Offset-Konstanten =====
Da Lyx keine generischen Structs mit dynamischer Größe im ABI kennt, werden interne Datenstrukturen als **flache Byte-Puffer** mit benannten Offset-Konstanten implementiert. Das Muster durchzieht die gesamte Standardbibliothek:
// Konzeptueller Aufbau einer internen Datenstruktur (Beispiel)
//
// Offset Größe Feld
// 0 8 fd (Dateideskriptor)
// 8 8 buf (Zeiger auf Lesepuffer)
// 16 8 bufLen (belegte Bytes im Puffer)
// 24 8 flags
//
// Gesamtgröße: 32 Bytes
pub con MY_STRUCT_SIZE: int64 := 32;
pub con MY_STRUCT_FD: int64 := 0;
pub con MY_STRUCT_BUF: int64 := 8;
pub con MY_STRUCT_BUFLEN: int64 := 16;
pub con MY_STRUCT_FLAGS: int64 := 24;
fn CreateMyStruct(fd: int64): int64 {
var s: int64 := alloc(MY_STRUCT_SIZE);
if (s == 0) { return 0; }
poke64(s + MY_STRUCT_FD, fd);
poke64(s + MY_STRUCT_BUF, 0);
poke64(s + MY_STRUCT_BUFLEN, 0);
poke64(s + MY_STRUCT_FLAGS, 0);
return s;
}
fn GetFd(s: int64): int64 {
return peek64(s + MY_STRUCT_FD);
}
fn SetBufLen(s: int64, len: int64): void {
poke64(s + MY_STRUCT_BUFLEN, len);
}
fn DestroyMyStruct(s: int64): void {
var buf: int64 := peek64(s + MY_STRUCT_BUF);
if (buf != 0) { free(buf, 4096); } // innere Ressourcen zuerst
free(s, MY_STRUCT_SIZE);
}
Dasselbe Muster findet sich in ''std.db.sqlite'' (''SQLiteDB'', 24 Bytes), ''std.db.postgres'' (''PGConn'', 120 Bytes), allen PDF-Untermodulen und allen Netzwerk-Units.
----
===== Byte-Arrays befüllen und lesen =====
import std.alloc;
fn ZeroFill(ptr: int64, n: int64): void {
var i: int64 := 0;
while (i < n) {
poke8(ptr + i, 0);
i := i + 1;
}
}
fn CopyBytes(dst: int64, src: int64, n: int64): void {
var i: int64 := 0;
while (i < n) {
poke8(dst + i, peek8(src + i));
i := i + 1;
}
}
fn ReadInt32BE(ptr: int64, off: int64): int64 {
// Big-Endian uint32 lesen (Netzwerk-Byteorder)
return (peek8(ptr + off) << 24) |
(peek8(ptr + off + 1) << 16) |
(peek8(ptr + off + 2) << 8) |
peek8(ptr + off + 3);
}
fn WriteInt32BE(ptr: int64, off: int64, v: int64): void {
poke8(ptr + off, (v >> 24) & 0xFF);
poke8(ptr + off + 1, (v >> 16) & 0xFF);
poke8(ptr + off + 2, (v >> 8) & 0xFF);
poke8(ptr + off + 3, v & 0xFF);
}
----
===== Dynamisch wachsende Puffer =====
Das Standard-Muster für wachsende Puffer in der Bibliothek — analoges Verhalten zu einem StringBuilder oder einem dynamischen Array:
import std.alloc;
// Typische Puffer-Verwaltung wie in std.pdf.builder, std.db.postgres, …
fn GrowBuf(oldBuf: int64, oldCap: int64, needed: int64): int64 {
var newCap: int64 := oldCap * 2;
while (newCap < needed) { newCap := newCap * 2; }
var newBuf: int64 := alloc(newCap);
if (newBuf == 0) { return 0; }
// Inhalt kopieren
var i: int64 := 0;
while (i < oldCap) {
poke8(newBuf + i, peek8(oldBuf + i));
i := i + 1;
}
free(oldBuf, oldCap);
return newBuf;
}
----
===== Freigabe-Reihenfolge =====
Bei verschachtelten Strukturen (Struct enthält Zeiger auf weiteren Heap-Speicher) gilt immer: **von innen nach außen freigeben**.
// Falsch — Memory Leak:
fn LeakyFree(conn: int64): void {
free(conn, CONN_SIZE); // conn wird freigegeben, aber
// conn.buf wurde nicht freigegeben → Leak
}
// Richtig:
fn CorrectFree(conn: int64): void {
var buf: int64 := peek64(conn + CONN_BUF);
if (buf != 0) {
free(buf, peek64(conn + CONN_BUFCAP)); // erst innere Ressource
}
free(conn, CONN_SIZE); // dann äußere Struktur
}
----
===== Warum dieser Ansatz? =====
Begründung für die gewählte Implementierungsstrategie:
* **Deterministische Performance** — kein GC, kein Stop-the-world, keine versteckten Allokationen
* **Null Overhead** — ein ''alloc'' ist ein Syscall (mmap) oder ein einfacher Pointer-Bump; ''peek''/''poke'' sind direkte Load/Store-Instruktionen
* **Portierbarkeit** — das Muster funktioniert identisch auf x86-64, ARM64 und Android (Bionic)
* **Nachvollziehbarkeit** — jeder Speicherzugriff ist im Quellcode sichtbar; kein Magic Proxy, kein Reflection
----
===== Zusammenfassung =====
^ Aufgabe ^ Mittel ^
| Puffer anlegen | ''alloc(n)'' → ''int64''-Adresse |
| Puffer freigeben | ''free(ptr, n)'' — Größe muss stimmen |
| Einzelbyte lesen/schreiben | ''peek8(addr)'' / ''poke8(addr, v)'' |
| 8-Byte-Wort lesen/schreiben | ''peek64(addr)'' / ''poke64(addr, v)'' |
| Strukturfeld lesen | ''peek64(base + OFFSET)'' |
| Strukturfeld schreiben | ''poke64(base + OFFSET, v)'' |
| Mehrere Ressourcen freigeben | Innen zuerst, außen zuletzt |
→ [[lyx_-_programmiersprache:sprache:handles-und-fd|Handle-Konzept — warum alles ein int64 ist]]\\
→ [[lyx_-_programmiersprache:units:alloc|std.alloc — Funktionsreferenz]]\\
→ [[lyx_-_programmiersprache:sprache:memory-management|Memory Management — Stack, Heap, Safety]]
Letzte Aktualisierung: 2026-06-05