Die Verwendung einer strafferen Sprache verschiebt nicht nur die Zielpfosten von der korrekten Implementierung zur richtigen Spezifikation. Es ist schwer, etwas zu machen, das sehr falsch und doch logisch konsistent ist. Deshalb fangen Compiler so viele Fehler ab.
Zeigerarithmetik, wie sie normalerweise formuliert wird, ist nicht stichhaltig, da das Typsystem nicht wirklich bedeutet, was es bedeuten soll. Sie können dieses Problem vollständig vermeiden, indem Sie in einer Sprache arbeiten, in der Müll gesammelt wird (der normale Ansatz, bei dem Sie auch für die Abstraktion bezahlen). Oder Sie können viel genauer festlegen, welche Arten von Zeigern Sie verwenden, sodass der Compiler alles ablehnen kann, was inkonsistent ist oder einfach nicht als richtig erwiesen werden kann. Dies ist der Ansatz einiger Sprachen wie Rust.
Konstruierte Typen sind gleichbedeutend mit Beweisen. Wenn Sie also ein Typensystem schreiben, das dies vergisst, gehen alle möglichen Dinge schief. Nehmen wir für eine Weile an, wenn wir einen Typ deklarieren, meinen wir tatsächlich, dass wir die Wahrheit über das, was in der Variablen enthalten ist, behaupten.
- int * x; // Eine falsche Behauptung. x existiert und zeigt nicht auf ein int
- int * y = z; // Nur wahr, wenn nachgewiesen wird, dass z auf ein int zeigt
- * (x + 3) = 5; // Nur wahr, wenn (x + 3) auf ein int im selben Array wie x zeigt
- int c = a / b; // Nur wahr, wenn b ungleich Null ist, wie: "ungleich Null int b = ...;"
- nullable int * z = NULL; // nullable int * ist nicht dasselbe wie int *
- int d = * z; // Eine falsche Behauptung, weil z nullbar ist
- if (z! = NULL) {int * e = z; } // Ok, weil z nicht null ist
- frei (y); int w = * y; // Falsche Behauptung, weil y bei w nicht mehr existiert
In dieser Welt können Zeiger nicht null sein. NullPointer-Dereferenzen existieren nicht und Zeiger müssen nirgendwo auf Null geprüft werden. Stattdessen ist ein "nullable int *" ein anderer Typ, dessen Wert entweder auf null oder auf einen Zeiger extrahiert werden kann. Dies bedeutet, dass Sie an dem Punkt, an dem die Nicht-Null- Annahme beginnt, entweder Ihre Ausnahme protokollieren oder einen Null-Zweig durchlaufen.
In dieser Welt gibt es auch keine Array-Out-of-Bound-Fehler. Wenn der Compiler nicht beweisen kann, dass er in Grenzen liegt, versuchen Sie, ihn neu zu schreiben, damit der Compiler dies beweisen kann. Wenn dies nicht möglich ist, müssen Sie die Annahme an dieser Stelle manuell eingeben. Der Compiler kann später einen Widerspruch dazu finden.
Wenn Sie keinen Zeiger haben können, der nicht initialisiert ist, haben Sie auch keine Zeiger auf den nicht initialisierten Speicher. Wenn Sie einen Zeiger auf freigegebenen Speicher haben, sollte dieser vom Compiler abgelehnt werden. In Rust gibt es verschiedene Zeigertypen, um diese Art von Beweisen vernünftig zu erwarten. Es gibt ausschließlich eigene Zeiger (dh keine Aliase), Zeiger auf tief unveränderliche Strukturen. Der Standardspeichertyp ist unveränderlich usw.
Es gibt auch das Problem, eine tatsächlich genau definierte Grammatik für Protokolle (einschließlich Schnittstellenelementen) durchzusetzen, um die Eingabeoberfläche auf genau das zu beschränken, was erwartet wird. Die Sache mit "Korrektheit" ist: 1) Alle undefinierten Zustände loswerden 2) Logische Konsistenz sicherstellen . Die Schwierigkeit, dorthin zu gelangen, hat viel mit der Verwendung extrem schlechter Werkzeuge zu tun (unter dem Gesichtspunkt der Korrektheit).
Dies ist genau der Grund, warum die beiden schlechtesten Praktiken globale Variablen und Gotos sind. Diese Dinge verhindern, dass vor / nach / invariante Bedingungen um irgendetwas gelegt werden. Es ist auch der Grund, warum Typen so effektiv sind. Wenn Typen stärker werden (wobei letztendlich abhängige Typen verwendet werden, um den tatsächlichen Wert zu berücksichtigen), nähern sie sich als konstruktive Korrektheitsbeweise an sich. Inkonsistente Programme können nicht kompiliert werden.
Denken Sie daran, dass es nicht nur um dumme Fehler geht. Es geht auch darum, die Codebasis vor cleveren Infiltratoren zu schützen. Es wird Fälle geben, in denen Sie eine Einreichung ohne einen überzeugenden maschinengenerierten Nachweis wichtiger Eigenschaften wie "folgt dem formal festgelegten Protokoll" ablehnen müssen.