Kapitel 1.12: Was NICHT existiert (Fallstricke)
Startseite | << Zurück: Fehlerbehandlung | Fallstricke | Weiter: Funktionen & Methoden >>
Inhaltsverzeichnis
- Vollständige Fallstrick-Referenz
- Kein ternärer Operator
- Keine do...while-Schleife
- Kein try/catch/throw
- Keine Mehrfachvererbung
- Kein Operator-Overloading (ausser Index)
- Keine Lambdas / anonymen Funktionen
- Keine Delegates / Funktionszeiger (nativ)
- Kein String-Escape für Backslash/Anführungszeichen
- Keine Variablen-Neudeklaration in else-if-Blöcken
- Kein Ternär in Variablendeklaration
- Keine mehrzeiligen Funktionsaufrufe
- Kein nullptr — verwenden Sie NULL oder null
- switch/case HAT Fall-Through
- Keine Standard-Parameter-Ausdrücke
- JsonFileLoader.JsonLoadFile gibt void zurück
- Kein #define-Wert-Substitution
- Keine Interfaces / abstrakte Klassen (erzwungen)
- Keine Generics-Einschränkungen
- Keine Enum-Validierung
- Keine variadischen Parameter
- Keine verschachtelten Klassendeklarationen
- Statische Arrays haben feste Größe
- array.Remove ist unsortiert
- Kein #include — alles via config.cpp
- Keine Namespaces
- String-Methoden modifizieren in-place
- ref-Zyklen verursachen Speicherlecks
- Keine Destruktor-Garantie beim Server-Shutdown
- Kein Scope-basiertes Ressourcenmanagement (RAII)
- GetGame().GetPlayer() gibt null auf dem Server zurück
sealed-Klassen können nicht erweitert werden (1.28+)- Methodenparameter-Limit: Maximal 16 (1.28+)
Obsolete-Attribut-Warnungen (1.28+)int.MIN-Vergleichsfehler- Boolesche Negation von Array-Elementen schlägt fehl
- Komplexer Ausdruck in Array-Zuweisung stürzt ab
foreachauf Methodenrückgabewert stürzt ab- Bitweise vs. Vergleichsoperator-Priorität
- Leere
#ifdef/#ifndef-Blöcke stürzen ab GetGame().IsClient()gibt false während des Ladens zurück- Kompilierfehler-Meldungen zeigen falsche Datei
crash_*.log-Dateien sind keine Abstürze
- Kommend von C++
- Kommend von C#
- Kommend von Java
- Kommend von Python
- Schnellreferenz-Tabelle
- Navigation
Vollständige Fallstrick-Referenz
1. Kein ternärer Operator
Was Sie schreiben würden:
int x = (condition) ? valueA : valueB;Was passiert: Kompilierfehler. Der ? :-Operator existiert nicht.
Korrekte Lösung:
int x;
if (condition)
x = valueA;
else
x = valueB;2. Keine do...while-Schleife
Was Sie schreiben würden:
do {
Process();
} while (HasMore());Was passiert: Kompilierfehler. Das do-Schlüsselwort existiert nicht.
Korrekte Lösung — Flag-Muster:
bool first = true;
while (first || HasMore())
{
first = false;
Process();
}Korrekte Lösung — Break-Muster:
while (true)
{
Process();
if (!HasMore())
break;
}3. Kein try/catch/throw
Was Sie schreiben würden:
try {
RiskyOperation();
} catch (Exception e) {
HandleError(e);
}Was passiert: Kompilierfehler. Diese Schlüsselwörter existieren nicht.
Korrekte Lösung: Guard-Klauseln mit frühzeitigem Return.
void DoOperation()
{
if (!CanDoOperation())
{
ErrorEx("Kann Operation nicht ausführen", ErrorExSeverity.WARNING);
return;
}
// Sicher fortfahren
RiskyOperation();
}Siehe Kapitel 1.11 — Fehlerbehandlung für vollständige Muster.
4. Keine Mehrfachvererbung
Was Sie schreiben würden:
class MyClass extends BaseA, BaseB // Zwei BasisklassenWas passiert: Kompilierfehler. Nur Einfachvererbung wird unterstützt.
Korrekte Lösung: Von einer Klasse erben, die andere zusammensetzen:
class MyClass extends BaseA
{
ref BaseB m_Helper;
void MyClass()
{
m_Helper = new BaseB();
}
}5. Kein Operator-Overloading (ausser Index)
Was Sie schreiben würden:
Vector3 operator+(Vector3 a, Vector3 b) { ... }
bool operator==(MyClass other) { ... }Was passiert: Kompilierfehler. Eigene Operatoren können nicht definiert werden.
Korrekte Lösung: Benannte Methoden verwenden:
class MyVector
{
float x, y, z;
MyVector Add(MyVector other)
{
MyVector result = new MyVector();
result.x = x + other.x;
result.y = y + other.y;
result.z = z + other.z;
return result;
}
bool Equals(MyVector other)
{
return (x == other.x && y == other.y && z == other.z);
}
}Ausnahme: Der Index-Operator [] kann über Get(index)- und Set(index, value)-Methoden überladen werden:
class MyContainer
{
int data[10];
int Get(int index) { return data[index]; }
void Set(int index, int value) { data[index] = value; }
}
MyContainer c = new MyContainer();
c[3] = 42; // Ruft Set(3, 42) auf
int v = c[3]; // Ruft Get(3) auf6. Keine Lambdas / anonymen Funktionen
Was Sie schreiben würden:
array.Sort((a, b) => a.name.CompareTo(b.name));
button.OnClick += () => { DoSomething(); };Was passiert: Kompilierfehler. Lambda-Syntax existiert nicht.
Korrekte Lösung: Benannte Methoden definieren und als ScriptCaller übergeben oder string-basierte Callbacks verwenden:
// Benannte Methode
void OnButtonClick()
{
DoSomething();
}
// String-basierter Callback (von CallLater, Timern etc. verwendet)
GetGame().GetCallQueue(CALL_CATEGORY_GAMEPLAY).CallLater(this.OnButtonClick, 1000, false);7. Keine Delegates / Funktionszeiger (nativ)
Was Sie schreiben würden:
delegate void MyCallback(int value);
MyCallback cb = SomeFunction;
cb(42);Was passiert: Kompilierfehler. Das delegate-Schlüsselwort existiert nicht.
Korrekte Lösung: ScriptCaller, ScriptInvoker oder string-basierte Methodennamen verwenden:
// ScriptCaller (einzelner Callback)
ScriptCaller caller = ScriptCaller.Create(MyFunction);
// ScriptInvoker (Event mit mehreren Abonnenten)
ref ScriptInvoker m_OnEvent = new ScriptInvoker();
m_OnEvent.Insert(MyHandler);
m_OnEvent.Invoke(); // Ruft alle registrierten Handler auf8. Kein String-Escape für Backslash/Anführungszeichen
Was Sie schreiben würden:
string path = "C:\\Users\\folder";
string quote = "He said \"hello\"";Was passiert: CParser stürzt ab oder erzeugt fehlerhaften Output. Die \\- und \"-Escape-Sequenzen brechen den String-Parser.
Korrekte Lösung: Backslash- und Anführungszeichen in String-Literalen vollständig vermeiden:
// Schrägstriche für Pfade verwenden
string path = "C:/Users/folder";
// Einfache Anführungszeichen verwenden oder umformulieren, um eingebettete doppelte Anführungszeichen zu vermeiden
string quote = "He said 'hello'";
// String-Verkettung verwenden, wenn Sie absolut Sonderzeichen brauchen
// (immer noch riskant — gruendlich testen)Hinweis:
\n-,\r- und\t-Escape-Sequenzen funktionieren. Nur\\und\"sind fehlerhaft.
9. Keine Variablen-Neudeklaration in else-if-Blöcken
Was Sie schreiben würden:
if (condA)
{
string msg = "Fall A";
Print(msg);
}
else if (condB)
{
string msg = "Fall B"; // Gleicher Variablenname im Geschwisterblock
Print(msg);
}Was passiert: Kompilierfehler: "multiple declaration of variable 'msg'". Enforce Script behandelt Variablen in geschwisterlichen if/else if/else-Blöcken als im selben Scope befindlich.
Korrekte Lösung — eindeutige Namen:
if (condA)
{
string msgA = "Fall A";
Print(msgA);
}
else if (condB)
{
string msgB = "Fall B";
Print(msgB);
}Korrekte Lösung — vor dem if deklarieren:
string msg;
if (condA)
{
msg = "Fall A";
}
else if (condB)
{
msg = "Fall B";
}
Print(msg);10. Kein Ternär in Variablendeklaration
Verwandt mit Fallstrick #1, aber spezifisch für Deklarationen:
Was Sie schreiben würden:
string label = isAdmin ? "Admin" : "Player";Korrekte Lösung:
string label;
if (isAdmin)
label = "Admin";
else
label = "Player";11. Keine mehrzeiligen Funktionsaufrufe
Was Sie schreiben würden:
string msg = string.Format(
"Player %1 at %2",
name,
pos
);Was passiert: Kompilierfehler. Der Parser von Enforce Script kann Funktionsaufrufe, die über mehrere Zeilen verteilt sind, nicht zuverlässig verarbeiten.
Korrekte Lösung:
string msg = string.Format("Player %1 at %2", name, pos);Halten Sie Funktionsaufrufe auf einer einzigen Zeile. Wenn die Zeile zu lang ist, teilen Sie die Arbeit in Zwischenvariablen auf.
12. Kein nullptr — verwenden Sie NULL oder null
Was Sie schreiben würden:
if (obj == nullptr)Was passiert: Kompilierfehler. Das nullptr-Schlüsselwort existiert nicht.
Korrekte Lösung:
if (obj == null) // Kleinbuchstaben funktioniert
if (obj == NULL) // Großbuchstaben funktioniert auch
if (!obj) // Idiomatische Null-Pruefung (bevorzugt)13. switch/case HAT Fall-Through
Enforce Script switch/case HAT Fall-Through, wenn break weggelassen wird, genau wie C/C++. Vanilla-Code nutzt Fall-Through absichtlich (biossessionservice.c:182 enthält den Kommentar "Intentionally no break, fall through to connecting").
Was Sie schreiben würden:
switch (value)
{
case 1:
case 2:
case 3:
Print("1, 2 oder 3"); // Alle drei Cases erreichen dies — Fall-Through funktioniert
break;
}Dies funktioniert wie erwartet. Cases 1 und 2 fallen durch zum Handler von Case 3.
Der Fallstrick: break vergessen, wenn Sie Fall-Through NICHT wollen:
switch (state)
{
case 0:
Print("Null");
// Fehlendes break! Fällt durch zu Case 1
case 1:
Print("Eins");
break;
}
// Wenn state == 0, gibt BEIDES "Null" und "Eins" ausRegel: Verwenden Sie immer break am Ende jedes Cases, es sei denn, Sie wollen absichtlich Fall-Through. Wenn Sie Fall-Through wollen, fügen Sie einen Kommentar hinzu, um dies klarzustellen.
14. Keine Standard-Parameter-Ausdrücke
Was Sie schreiben würden:
void Spawn(vector pos = GetDefaultPos()) // Ausdruck als Standard
void Spawn(vector pos = Vector(0, 100, 0)) // Konstruktor als StandardWas passiert: Kompilierfehler. Standardparameterwerte müssen Literale oder NULL sein.
Korrekte Lösung:
void Spawn(vector pos = "0 100 0") // String-Literal für Vektor — OK
void Spawn(int count = 5) // Integer-Literal — OK
void Spawn(float radius = 10.0) // Float-Literal — OK
void Spawn(string name = "default") // String-Literal — OK
void Spawn(Object obj = NULL) // NULL — OK
// Für komplexe Standards, Überladungen verwenden:
void Spawn()
{
Spawn(GetDefaultPos()); // Die parametrische Version aufrufen
}
void Spawn(vector pos)
{
// Eigentliche Implementierung
}15. JsonFileLoader.JsonLoadFile gibt void zurück
Was Sie schreiben würden:
MyConfig cfg = JsonFileLoader<MyConfig>.JsonLoadFile(path);
// oder:
if (JsonFileLoader<MyConfig>.JsonLoadFile(path, cfg))Was passiert: Kompilierfehler. JsonLoadFile gibt void zurück, nicht das geladene Objekt oder einen bool.
Korrekte Lösung:
MyConfig cfg = new MyConfig(); // Zuerst Instanz mit Standardwerten erstellen
JsonFileLoader<MyConfig>.JsonLoadFile(path, cfg); // Befüllt cfg in-place
// cfg enthält jetzt geladene Werte (oder hat immer noch Standards, wenn die Datei ungültig war)Hinweis: Die neuere
JsonFileLoader<T>.LoadFile()-Methode gibtboolzurück, aberJsonLoadFile(die häufig gesehene Version) tut dies nicht.
16. Kein #define-Wert-Substitution
Was Sie schreiben würden:
#define MAX_PLAYERS 60
#define VERSION_STRING "1.0.0"
int max = MAX_PLAYERS;Was passiert: Kompilierfehler. Enforce Script #define erstellt nur Existenz-Flags für #ifdef-Pruefungen. Es unterstützt keine Wert-Substitution.
Korrekte Lösung:
// const für Werte verwenden
const int MAX_PLAYERS = 60;
const string VERSION_STRING = "1.0.0";
// #define nur für bedingte Kompilierungs-Flags verwenden
#define MY_MOD_ENABLED17. Keine Interfaces / abstrakte Klassen (erzwungen)
Was Sie schreiben würden:
interface ISerializable
{
void Serialize();
void Deserialize();
}
abstract class BaseProcessor
{
abstract void Process();
}Was passiert: Die Schlüsselwörter interface und abstract existieren nicht.
Korrekte Lösung: Reguläre Klassen mit leeren Basismethoden verwenden:
// "Interface" — Basisklasse mit leeren Methoden
class ISerializable
{
void Serialize() {} // In Unterklasse überschreiben
void Deserialize() {} // In Unterklasse überschreiben
}
// "Abstrakte" Klasse — gleiches Muster
class BaseProcessor
{
void Process()
{
ErrorEx("BaseProcessor.Process() muss überschrieben werden!", ErrorExSeverity.ERROR);
}
}
class ConcreteProcessor extends BaseProcessor
{
override void Process()
{
// Tatsächliche Implementierung
}
}Der Compiler erzwingt NICHT, dass Unterklassen die Basismethoden überschreiben. Das Vergessen des Überschreibens verwendet stillschweigend die leere Basisimplementierung.
18. Keine Generics-Einschränkungen
Was Sie schreiben würden:
class Container<T> where T : EntityAI // T auf EntityAI einschränkenWas passiert: Kompilierfehler. Die where-Klausel existiert nicht. Template-Parameter akzeptieren jeden Typ.
Korrekte Lösung: Zur Laufzeit validieren:
class EntityContainer<Class T>
{
void Add(T item)
{
// Laufzeit-Typpruefung statt Kompilierzeit-Einschränkung
EntityAI eai;
if (!Class.CastTo(eai, item))
{
ErrorEx("EntityContainer akzeptiert nur EntityAI-Unterklassen");
return;
}
// fortfahren
}
}19. Keine Enum-Validierung
Was Sie schreiben würden:
EDamageState state = (EDamageState)999; // Fehler oder Ausnahme erwartenWas passiert: Kein Fehler. Jeder int-Wert kann einer Enum-Variable zugewiesen werden, auch Werte ausserhalb des definierten Bereichs.
Korrekte Lösung: Manuell validieren:
bool IsValidDamageState(int value)
{
return (value >= EDamageState.PRISTINE && value <= EDamageState.RUINED);
}
int rawValue = LoadFromConfig();
if (IsValidDamageState(rawValue))
{
EDamageState state = rawValue;
}
else
{
Print("Ungültiger Schadenszustand: " + rawValue.ToString());
EDamageState state = EDamageState.PRISTINE; // Fallback
}20. Keine variadischen Parameter
Was Sie schreiben würden:
void Log(string format, params object[] args)
void Printf(string fmt, ...)Was passiert: Kompilierfehler. Variadische Parameter existieren nicht.
Korrekte Lösung: string.Format mit fester Parameteranzahl verwenden, oder Param-Klassen nutzen:
// string.Format unterstützt bis zu 9 Positionsargumente
string msg = string.Format("Spieler %1 bei %2 mit %3 HP", name, pos, hp);
// Für variable Datenmenge ein Array übergeben
void LogMultiple(string tag, array<string> messages)
{
foreach (string msg : messages)
{
Print("[" + tag + "] " + msg);
}
}21. Keine verschachtelten Klassendeklarationen
Was Sie schreiben würden:
class Outer
{
class Inner // Verschachtelte Klasse
{
int value;
}
}Was passiert: Kompilierfehler. Klassen können nicht innerhalb anderer Klassen deklariert werden.
Korrekte Lösung: Alle Klassen auf oberster Ebene deklarieren, Namenskonventionen verwenden, um Beziehungen zu zeigen:
class MySystem_Config
{
int value;
}
class MySystem
{
ref MySystem_Config m_Config;
}22. Statische Arrays haben feste Größe
Was Sie schreiben würden:
int size = GetCount();
int arr[size]; // Dynamische Größe zur LaufzeitWas passiert: Kompilierfehler. Statische Array-Größen müssen Kompilierzeit-Konstanten sein.
Korrekte Lösung:
// Eine Konstante für statische Arrays verwenden
const int BUFFER_SIZE = 64;
int arr[BUFFER_SIZE];
// Oder dynamische Arrays für Laufzeit-Größenanpassung verwenden
array<int> arr = new array<int>;
arr.Resize(GetCount());23. array.Remove ist unsortiert
Was Sie schreiben würden (Reihenfolge-Erhaltung erwartend):
array<string> items = {"A", "B", "C", "D"};
items.Remove(1); // Erwartet: {"A", "C", "D"}Was passiert: Remove(index) tauscht das Element mit dem letzten Element und entfernt dann das letzte. Ergebnis: {"A", "D", "C"}. Die Reihenfolge wird NICHT beibehalten.
Korrekte Lösung:
// RemoveOrdered für Reihenfolge-Erhaltung verwenden (langsamer — verschiebt Elemente)
items.RemoveOrdered(1); // {"A", "C", "D"} — korrekte Reihenfolge
// RemoveItem zum Finden und Entfernen nach Wert verwenden (ebenfalls geordnet)
items.RemoveItem("B"); // {"A", "C", "D"}24. Kein #include — alles via config.cpp
Was Sie schreiben würden:
#include "MyHelper.c"
#include "Utils/StringUtils.c"Was passiert: Keine Wirkung oder Kompilierfehler. Es gibt keine #include-Direktive.
Korrekte Lösung: Alle Skriptdateien werden über config.cpp im CfgMods-Eintrag der Mod geladen. Die Dateiladereihenfolge wird durch die Skriptschicht (3_Game, 4_World, 5_Mission) und die alphabetische Reihenfolge innerhalb jeder Schicht bestimmt.
// config.cpp
class CfgMods
{
class MyMod
{
type = "mod";
dependencies[] = { "Game", "World", "Mission" };
class defs
{
class gameScriptModule
{
files[] = { "MyMod/Scripts/3_Game" };
};
class worldScriptModule
{
files[] = { "MyMod/Scripts/4_World" };
};
class missionScriptModule
{
files[] = { "MyMod/Scripts/5_Mission" };
};
};
};
};25. Keine Namespaces
Was Sie schreiben würden:
namespace MyMod { class Config { } }
namespace MyMod.Utils { class StringHelper { } }Was passiert: Kompilierfehler. Das namespace-Schlüsselwort existiert nicht. Alle Klassen teilen einen einzigen globalen Scope.
Korrekte Lösung: Namenspräfixe verwenden, um Konflikte zu vermeiden:
class MyConfig { } // MyFramework
class MyAI_Config { } // MyAI-Mod
class MyM_MissionData { } // MyMissions-Mod
class VPP_AdminConfig { } // VPP Admin26. String-Methoden modifizieren in-place
Was Sie schreiben würden (einen Rückgabewert erwartend):
string upper = myString.ToUpper(); // Erwartet: gibt neuen String zurückWas passiert: ToUpper() und ToLower() modifizieren den String in-place und geben int zurück (die Laenge des geaenderten Strings), nicht einen neuen String. Aus enstring.c: proto int ToLower(); und proto int ToUpper();.
Korrekte Lösung:
// Zuerst eine Kopie machen, wenn Sie das Original beibehalten müssen
string original = "Hello World";
string upper = original;
upper.ToUpper(); // upper ist jetzt "HELLO WORLD", original unverändert
// Gleich für TrimInPlace
string trimmed = " hello ";
trimmed.TrimInPlace(); // "hello"27. ref-Zyklen verursachen Speicherlecks
Was Sie schreiben würden:
class Parent
{
ref Child m_Child;
}
class Child
{
ref Parent m_Parent; // Zirkuläre ref — beide referenzieren einander
}Was passiert: Keines der Objekte wird jemals vom Garbage Collector erfasst. Die Referenzzähler erreichen nie Null, weil jedes eine ref auf das andere hält.
Korrekte Lösung: Eine Seite muss einen rohen (nicht-ref) Zeiger verwenden:
class Parent
{
ref Child m_Child; // Parent BESITZT das Kind (ref)
}
class Child
{
Parent m_Parent; // Kind REFERENZIERT das Elternteil (roh — kein ref)
}28. Keine Destruktor-Garantie beim Server-Shutdown
Was Sie schreiben würden (Bereinigung erwartend):
void ~MyManager()
{
SaveData(); // Erwartet, dass dies beim Shutdown läuft
}Was passiert: Server-Shutdown kann den Prozess beenden, bevor Destruktoren laufen. Ihr Speichern geschieht nie.
Korrekte Lösung: Proaktiv in regelmäßigen Abständen und bei bekannten Lebenszyklus-Events speichern:
class MyManager
{
void OnMissionFinish() // Wird vor dem Shutdown aufgerufen
{
SaveData(); // Zuverlässiger Speicherpunkt
}
void OnUpdate(float dt)
{
m_SaveTimer += dt;
if (m_SaveTimer > 300.0) // Alle 5 Minuten
{
SaveData();
m_SaveTimer = 0;
}
}
}29. Kein Scope-basiertes Ressourcenmanagement (RAII)
Was Sie schreiben würden (in C++):
{
FileHandle f = OpenFile("test.txt", FileMode.WRITE);
// f wird automatisch geschlossen, wenn der Scope endet
}Was passiert: Enforce Script schliesst Dateihandles nicht, wenn Variablen den Scope verlassen (auch nicht mit autoptr).
Korrekte Lösung: Ressourcen immer explizit schliessen:
FileHandle fh = OpenFile("$profile:MyMod/data.txt", FileMode.WRITE);
if (fh != 0)
{
FPrintln(fh, "data");
CloseFile(fh); // Muss manuell geschlossen werden!
}30. GetGame().GetPlayer() gibt null auf dem Server zurück
Was Sie schreiben würden:
PlayerBase player = PlayerBase.Cast(GetGame().GetPlayer());
player.DoSomething(); // ABSTURZ auf dem Server!Was passiert: GetGame().GetPlayer() gibt den lokalen Spieler zurück. Auf einem dedizierten Server gibt es keinen lokalen Spieler — es gibt null zurück.
Korrekte Lösung: Auf dem Server die Spielerliste durchiterieren:
#ifdef SERVER
array<Man> players = new array<Man>;
GetGame().GetPlayers(players);
foreach (Man man : players)
{
PlayerBase player;
if (Class.CastTo(player, man))
{
player.DoSomething();
}
}
#else
PlayerBase player = PlayerBase.Cast(GetGame().GetPlayer());
if (player)
{
player.DoSomething();
}
#endif31. sealed-Klassen können nicht erweitert werden (1.28+)
Ab DayZ 1.28 erzwingt der Enforce-Script-Compiler das sealed-Schlüsselwort. Eine Klasse oder Methode, die als sealed markiert ist, kann nicht vererbt oder überschrieben werden. Wenn Sie versuchen, eine sealed-Klasse zu erweitern:
// Wenn BI SomeVanillaClass als sealed markiert:
class MyClass : SomeVanillaClass // KOMPILIERFEHLER in 1.28+
{
}Prüfen Sie den Vanilla-Script-Dump auf Klassen, die als sealed markiert sind, bevor Sie versuchen, von ihnen zu erben. Wenn Sie das Verhalten einer sealed-Klasse ändern müssen, verwenden Sie Komposition (umschliessen Sie die Klasse als Mitglied) anstelle von Vererbung.
32. Methodenparameter-Limit: Maximal 16 (1.28+)
Enforce Script hatte schon immer ein 16-Parameter-Limit für Methoden, aber vor 1.28 war es ein stiller Pufferüberlauf, der zufällige Abstürze verursachte. Ab 1.28 erzeugt der Compiler einen harten Fehler:
// KOMPILIERFEHLER in 1.28+ — überschreitet 16 Parameter
void MyMethod(int a, int b, int c, int d, int e, int f, int g, int h,
int i, int j, int k, int l, int m, int n, int o, int p,
int q) // 17. Parameter = Fehler
{
}Lösung: Refaktorieren Sie, um stattdessen eine Klasse oder ein Array zu übergeben.
33. Obsolete-Attribut-Warnungen (1.28+)
DayZ 1.28 führte das Obsolete-Attribut ein. Funktionen und Klassen, die mit [Obsolete] markiert sind, erzeugen Compiler-Warnungen. Diese APIs funktionieren noch, sind aber für die Entfernung in einem zukünftigen Update vorgesehen. Prüfen Sie Ihre Build-Ausgabe auf Obsolete-Warnungen und migrieren Sie zum empfohlenen Ersatz.
34. int.MIN-Vergleichsfehler
Vergleiche mit int.MIN (-2147483648) erzeugen falsche Ergebnisse:
int val = 1;
if (val < int.MIN) // Ergibt TRUE — sollte false sein
{
// Dieser Block wird fälschlicherweise ausgeführt
}Vermeiden Sie direkte Vergleiche mit int.MIN. Verwenden Sie stattdessen eine gespeicherte Konstante oder vergleichen Sie mit einem bestimmten negativen Wert.
35. Boolesche Negation von Array-Elementen schlägt fehl
Direkte boolesche Negation von Array-Elementen kompiliert nicht:
array<int> list = {0, 1, 2};
if (!list[1]) // KOMPILIERT NICHT
if (list[1] == 0) // Funktioniert — expliziten Vergleich verwendenVerwenden Sie immer explizite Gleichheitsprüfungen, wenn Sie Array-Elemente auf Wahrheitswert testen.
36. Komplexer Ausdruck in Array-Zuweisung stürzt ab
Das direkte Zuweisen eines komplexen Ausdrucks zu einem Array-Element kann einen Segmentierungsfehler verursachen:
// STUERZT ZUR LAUFZEIT AB
m_Values[index] = vector.DistanceSq(posA, posB) <= distSq;
// SICHER — Zwischenvariable verwenden
bool result = vector.DistanceSq(posA, posB) <= distSq;
m_Values[index] = result;Speichern Sie Ergebnisse komplexer Ausdrücke immer in einer lokalen Variablen, bevor Sie sie einem Array zuweisen.
37. foreach auf Methodenrückgabewert stürzt ab
Die Verwendung von foreach direkt auf dem Rückgabewert einer Methode verursacht eine Null-Zeiger-Ausnahme bei der zweiten Iteration:
// STUERZT BEIM 2. ELEMENT AB
foreach (string item : GetMyArray())
{
}
// SICHER — zuerst in lokaler Variable speichern
array<string> items = GetMyArray();
foreach (string item : items)
{
}38. Bitweise vs. Vergleichsoperator-Priorität
Bitweise Operatoren haben niedrigere Priorität als Vergleichsoperatoren, nach C/C++-Regeln:
int flags = 5;
int mask = 4;
if (flags & mask == mask) // FALSCH: wird als flags & (mask == mask) ausgewertet
if ((flags & mask) == mask) // RICHTIG: immer Klammern verwenden39. Leere #ifdef/#ifndef-Blöcke stürzen ab
Leere Präprozessor-Bedingungsblöcke — selbst solche, die nur Kommentare enthalten — verursachen Segmentierungsfehler:
#ifdef SOME_DEFINE
// Dieser nur-Kommentar-Block verursacht einen SEGFAULT
#endifFügen Sie immer mindestens eine ausführbare Anweisung ein oder entfernen Sie den Block vollständig.
40. GetGame().IsClient() gibt false während des Ladens zurück
Während der Client-Ladephase gibt GetGame().IsClient() false und GetGame().IsServer() true zurück — selbst auf Clients. Verwenden Sie stattdessen IsDedicatedServer():
// UNZUVERLAESSIG während der Ladephase
if (GetGame().IsClient()) { } // false während des Ladens!
if (GetGame().IsServer()) { } // true während des Ladens, selbst auf dem Client!
// ZUVERLAESSIG
if (!GetGame().IsDedicatedServer()) { /* Client-Code */ }
if (GetGame().IsDedicatedServer()) { /* Server-Code */ }Ausnahme: Wenn Sie Offline-/Singleplayer-Modus unterstützen müssen, gibt IsDedicatedServer() auch für Listen-Server false zurück.
41. Kompilierfehler-Meldungen zeigen falsche Datei
Wenn der Compiler auf eine undefinierte Klasse oder einen Variablen-Namenskonflikt stösst, meldet er den Fehler am EOF der zuletzt erfolgreich geparsten Datei — nicht am tatsächlichen Fehlerort. Wenn Sie einen Fehler sehen, der auf eine Datei zeigt, die Sie nicht geändert haben, liegt der echte Fehler in einer Datei, die direkt nach ihr geparst wurde.
42. crash_*.log-Dateien sind keine Abstürze
Log-Dateien mit dem Namen crash_<datum>_<zeit>.log enthalten Laufzeit-Ausnahmen, keine tatsächlichen Segmentierungsfehler. Die Benennung ist irreführend — es handelt sich um Script-Fehler, nicht um Engine-Abstürze.
Kommend von C++
Wenn Sie C++-Entwickler sind, hier die größten Umstellungen:
| C++-Feature | Enforce-Script-Äquivalent |
|---|---|
std::vector | array<T> |
std::map | map<K,V> |
std::unique_ptr | ref / autoptr |
dynamic_cast<T*> | Class.CastTo() oder T.Cast() |
try/catch | Guard-Klauseln |
operator+ | Benannte Methoden (Add()) |
namespace | Namenspräfixe (My, VPP_) |
#include | config.cpp files[] |
| RAII | Manuelle Bereinigung in Lebenszyklus-Methoden |
| Mehrfachvererbung | Einfachvererbung + Komposition |
nullptr | null / NULL |
| Templates mit Einschränkungen | Templates ohne Einschränkungen + Laufzeitpruefungen |
do...while | while (true) { ... if (!cond) break; } |
Kommend von C#
| C#-Feature | Enforce-Script-Äquivalent |
|---|---|
interface | Basisklasse mit leeren Methoden |
abstract | Basisklasse + ErrorEx in Basismethoden |
delegate / event | ScriptInvoker |
Lambda => | Benannte Methoden |
?. Null-Bedingung | Manuelle Null-Pruefungen |
?? Null-Koaleszenz | if (!x) x = default; |
try/catch | Guard-Klauseln |
using (IDisposable) | Manuelle Bereinigung |
Properties { get; set; } | Öffentliche Felder oder explizite Getter/Setter-Methoden |
| LINQ | Manuelle Schleifen |
nameof() | Fest kodierte Strings |
async/await | CallLater / Timer |
Kommend von Java
| Java-Feature | Enforce-Script-Äquivalent |
|---|---|
interface | Basisklasse mit leeren Methoden |
try/catch/finally | Guard-Klauseln |
| Garbage Collection | ref + Referenzzählung (kein GC für Zyklen) |
@Override | override-Schlüsselwort |
instanceof | obj.IsInherited(typename) |
package | Namenspräfixe |
import | config.cpp files[] |
enum mit Methoden | enum (nur int) + Hilfsklasse |
final | const (nur für Variablen) |
| Annotations | Nicht verfügbar |
Kommend von Python
| Python-Feature | Enforce-Script-Äquivalent |
|---|---|
| Dynamische Typisierung | Statische Typisierung (alle Variablen typisiert) |
try/except | Guard-Klauseln |
lambda | Benannte Methoden |
| List Comprehension | Manuelle Schleifen |
**kwargs / *args | Feste Parameter |
| Duck Typing | IsInherited() / Class.CastTo() |
__init__ | Konstruktor (gleicher Name wie Klasse) |
__del__ | Destruktor (~ClassName()) |
import | config.cpp files[] |
| Mehrfachvererbung | Einfachvererbung + Komposition |
None | null / NULL |
| Einrückungsbasierte Blöcke | { }-Klammern |
| f-strings | string.Format("text %1 %2", a, b) |
Schnellreferenz-Tabelle
| Feature | Existiert? | Workaround |
|---|---|---|
Ternär ? : | Nein | if/else |
do...while | Nein | while + break |
try/catch | Nein | Guard-Klauseln |
| Mehrfachvererbung | Nein | Komposition |
| Operator-Overloading | Nur Index | Benannte Methoden |
| Lambdas | Nein | Benannte Methoden |
| Delegates | Nein | ScriptInvoker |
\\ / \" in Strings | Fehlerhaft | Vermeiden |
| Variablen-Neudeklaration | Fehlerhaft in else-if | Eindeutige Namen oder vor if deklarieren |
| Mehrzeilige Funktionsaufrufe | Unzuverlässiges Parsing | Aufrufe auf einer Zeile halten |
nullptr | Nein | null / NULL |
| switch Fall-Through | Ja (wie C/C++) | Immer break verwenden, ausser bei Absicht |
| Standard-Param-Ausdrücke | Nein | Nur Literale oder NULL |
#define-Werte | Nein | const |
| Interfaces | Nein | Leere Basisklasse |
| Generic-Einschränkungen | Nein | Laufzeit-Typpruefungen |
| Enum-Validierung | Nein | Manuelle Bereichsprüfung |
| Variadische Params | Nein | string.Format oder Arrays |
| Verschachtelte Klassen | Nein | Oberste Ebene mit Namenspräfixen |
| Variable statische Arrays | Nein | array<T> |
#include | Nein | config.cpp files[] |
| Namespaces | Nein | Namenspräfixe |
| RAII | Nein | Manuelle Bereinigung |
GetGame().GetPlayer() Server | Gibt null zurück | GetPlayers() iterieren |
sealed-Klassenvererbung (1.28+) | Kompilierfehler | Stattdessen Komposition verwenden |
| 17+ Methodenparameter (1.28+) | Kompilierfehler | Klasse oder Array übergeben |
[Obsolete]-APIs (1.28+) | Compiler-Warnung | Zum Ersatz-API migrieren |
int.MIN-Vergleiche | Falsche Ergebnisse | Gespeicherte Konstante verwenden |
!array[i]-Negation | Kompilierfehler | array[i] == 0 verwenden |
| Komplexer Ausdruck in Array-Zuweisung | Segfault | Zwischenvariable verwenden |
foreach auf Methodenrückgabewert | Null-Zeiger-Absturz | Zuerst in lokaler Variable speichern |
| Bitweise vs. Vergleichspriorität | Falsche Auswertung | Immer Klammern verwenden |
Leere #ifdef-Blöcke | Segfault | Anweisung einfügen oder Block entfernen |
IsClient() während des Ladens | Gibt false zurück | IsDedicatedServer() verwenden |
| Kompilierfehler falsche Datei | Irreführender Ort | Datei prüfen, die nach der gemeldeten geparst wird |
crash_*.log-Dateien | Keine echten Abstürze | Es sind Laufzeit-Script-Ausnahmen |
Navigation
| Zurück | Hoch | Weiter |
|---|---|---|
| 1.11 Fehlerbehandlung | Teil 1: Enforce Script | 1.13 Funktionen & Methoden |
