Lyx – Arrays, Maps & Datensammlungen

Lyx unterscheidet vier Container-Formen. Die Wahl zwischen ihnen ist keine Stilfrage, sondern eine Sicherheits- und Architekturentscheidung: Stack-Arrays sind deterministisch und DO-178C-tauglich, Heap-Arrays sind flexibel aber nicht echtzeitfähig, SIMD-Arrays für numerische Hochleistungsberechnungen, Maps für Schlüssel-Wert-Zugriff.

Datentypen · Memory Management · DO-178C


1. Statische Arrays — [N]T

Statische Arrays liegen auf dem Stack. Größe und Typ stehen zur Compile-Zeit fest und sind unveränderlich. Das ist die bevorzugte Form für sicherheitskritischen und echtzeitfähigen Code.

Syntax

// [Größe]Typ — Typ-zuerst-Notation (wie Go/Zig, NICHT wie C)
var buffer:  [256]uint8 := [];              // 256 Bytes, nullinitialisiert
var coords:  [3]f64     := [0.0, 0.0, 0.0];
var ids:     [16]int64  := [];

// let — unveränderliche Bindung (Werte im Array selbst bleiben änderbar)
let defaults: [4]int64 := [0, 1, 2, 3];

Wichtig: Die Syntax ist [N]T — Größe in eckigen Klammern vor dem Typ. T[N] (C-Stil) ist in Lyx kein gültiger Typ.

Zugriff und Zuweisung

var coords: [3]f64 := [0.0, 0.0, 0.0];

coords[0] := 10.5;            // Schreiben
coords[1] := coords[0];       // Lesen und Schreiben
var z: f64 := coords[2];      // Lesen

// Bounds-Checking:
// — Konstanter Index außerhalb der Grenzen → Compile-Fehler
// — Variabler Index außerhalb der Grenzen  → kontrolliertes panic zur Laufzeit

Mehrdimensionale statische Arrays

// 8×8-Matrix — flaches Layout im Speicher (64 × 8 Bytes = 512 Bytes)
var grid: [8][8]int64 := [];
grid[3][4] := 99;

var val: int64 := grid[3][4];   // 99

Safety-Eigenschaften

  • Keine Heap-Allokation — kein Fragmentierungsrisiko, kein GC-Druck
  • Bounds-Checking bei konstantem Index zur Compile-Zeit
  • Bounds-Checking bei variablem Index zur Laufzeit (kontrollierbarer panic)
  • Maximaler Stack-Verbrauch statisch berechenbar → kompatibel mit @stack_limit
  • Kein dispose nötig — Stack-Frame wird automatisch freigegeben

// DO-178C DAL-A: Waypoint-Liste als statisches Array
@dal(A)
@flight_crit
@stack_limit(2048)
@wcet(100)
fn FindNearest(wps: [32]Waypoint, n: int64, pos: GeoPoint): int64 {
    var best: int64 := 0;
    var best_d: f64 := 1.0e18;
    for i := 0 to n - 1 do {
        var d: f64 := GeoDistance(pos, wps[i].pos);
        if (d < best_d) { best_d := d; best := i; }
    }
    return best;
}


2. Dynamische Arrays — array<T>

Dynamische Arrays liegen auf dem Heap und wachsen automatisch. Intern speichern sie drei Werte (Fat-Pointer): Speicheradresse, aktuelle Länge (len) und reservierte Kapazität (cap).

Syntax und Operationen

import std.io;

var list: array<int64> := [10, 20, 30];

list.push(40);                        // Anhängen
list.push(50);

PrintInt(list.len());                 // 5
PrintInt(list.cap());                 // Reservierte Kapazität (≥ len)
PrintInt(list[0]);                    // 10

list[0] := list[0] + 1;              // Lesen und Schreiben (kein ++ Operator in Lyx)
list[2] := 999;

list.clear();                         // len := 0, Speicher bleibt reserviert
PrintInt(list.len());                 // 0

dispose list;                         // Zwingend — sonst Memory Leak

dispose mit mehreren return-Pfaden

Verlässt eine Funktion über mehrere Pfade, muss dispose vor jedem return stehen:

fn LoadConfig(path: pchar): bool {
    var cfg: Map<pchar, int64> := {};

    if (ParseFile(path, cfg) = false) {
        dispose cfg;      // Auch im Fehlerfall freigeben
        return false;
    }

    ApplyConfig(cfg);
    dispose cfg;
    return true;
}

Lyx hat kein automatisches RAII/Destruktor-System für Heap-Typen. Bei mehreren return-Pfaden statische Arrays [N]T bevorzugen — kein dispose, kein Leak-Risiko.

Wann array<T> sinnvoll ist

  • Anzahl der Elemente zur Compile-Zeit unbekannt
  • Server- und Tool-Code, bei dem Flexibilität wichtiger ist als deterministisches Timing
  • Nicht in DAL-A/B-Regelschleifen — Heap-Allokation hat nicht-deterministische Latenz
In Funktionen mit @flight_crit warnt der Compiler bei Heap-Allokation. Ausschließlich [N]T verwenden.

3. Parallele Arrays — parallel Array<T>

Parallele Arrays sind Heap-Arrays mit garantiertem SIMD-Alignment (16 Byte für SSE/NEON, 32 Byte für AVX). Batch-Operationen auf diesen Arrays übersetzt der Compiler direkt in Vektor-Instruktionen.

var data: parallel Array<f64> := parallel Array<f64>(1024);

data[0] := 1.0;
data[1] := 2.0;

// Batch-Skalierung — compiler übersetzt in SSE/AVX/NEON
// data := data * 2.0;

dispose data;

Einsatzgebiete

  • Signalverarbeitung (FFT, Filter, Fensterfunktionen)
  • Lineare Algebra (Matrix-Vektor-Produkt)
  • Machine-Learning-Inferenz (Dot-Produkte, Aktivierungen)
  • Physik-Simulationen (Partikelsysteme, Kraftfelder)
  • Nur für primitive Typen: f32, f64, int32, int64
  • Kein Einsatz in DAL-A/B-Regelschleifen — SIMD-Timing nicht vorhersehbar genug

4. Maps — Map<K, V>

Maps erlauben den Zugriff über einen Schlüssel. Lyx bietet native Literal-Syntax seit v0.5.0.

Erstellen, Lesen, Schreiben

import std.io;

var scores: Map<pchar, int64> := {"Alpha": 100, "Bravo": 250, "Charlie": 175};

// Lesen
var s: int64 := scores["Alpha"];    // 100

// Schreiben / Einfügen
scores["Delta"] := 300;

// Existenzprüfung mit in — immer vor Zugriff auf unbekannte Schlüssel
if ("Delta" in scores) {
    PrintInt(scores["Delta"]);      // 300
    PrintStr("\n");
}

// Löschen eines Eintrags
delete scores["Charlie"];

dispose scores;

Iteration

var config: Map<pchar, int64> := {
    "timeout_ms":  5000,
    "retry_count": 3,
    "log_level":   2
};

for key, val in config do {
    PrintStr(key); PrintStr(": "); PrintInt(val); PrintStr("\n");
}
// Ausgabe (Reihenfolge implementierungsabhängig):
// timeout_ms: 5000
// retry_count: 3
// log_level: 2

dispose config;

Map<pchar, pchar>

var headers: Map<pchar, pchar> := {
    "Content-Type":  "application/json",
    "Authorization": "Bearer abc123"
};

if ("Content-Type" in headers) {
    PrintStr(headers["Content-Type"]);   // application/json
    PrintStr("\n");
}

dispose headers;

Performance-Modell

  • < 128 Einträge: Cache-optimierte lineare Suche — O(n), aber sehr schnell durch Lokalität
  • ≥ 128 Einträge: Hash-Verfahren — O(1) amortisiert, automatischer Wechsel

5. Speicherverwaltung — Heap-Container

Alle Heap-Container (array<T>, parallel Array<T>, Map<K,V>) müssen explizit freigegeben werden:

fn ProcessData(): void {
    var buf:  array<uint8>         := [];
    var data: parallel Array<f64>  := parallel Array<f64>(256);
    var meta: Map<pchar, int64>    := {};

    // ... Verarbeitung ...

    dispose buf;
    dispose data;
    dispose meta;
}

Wird dispose vergessen, entsteht ein Memory Leak — besonders in Langzeit-Systemen (Avionik, Server) kritisch.


6. Zusammenfassung & Safety-Matrix

Typ Syntax Ort Timing DO-178C DAL-A dispose
Statisches Array [N]T Stack Deterministisch Nein
Dynamisches Array array<T> Heap Nicht-deterministisch ✗ (nur Init) Ja
Paralleles Array parallel Array<T> Heap (SIMD) Nicht-deterministisch Ja
Map Map<K, V> Heap Nicht-deterministisch Ja
Eigenschaft [N]T array<T> parallel Array<T> Map<K,V>
Größe zur Compile-Zeit Ja Nein Nein Nein
Bounds-Checking Compile + Runtime Runtime Runtime Runtime
@stack_limit-kompatibel Ja Nein Nein Nein
SIMD-Vektorisierung Nein Nein Ja Nein
Schlüssel-Zugriff Nein Nein Nein Ja
for key, val in Nein Nein Nein Ja

Letzte Aktualisierung: 2026-05-22