Using a tighter language doesn't just move goal posts around from getting implementation correct to getting the spec right. It is hard to make something that is very wrong yet consistent logically; which is why compilers catch so many bugs.
Pointer Arithmetic as it is normally formulated is unsound because the type system doesn't actually mean what it is supposed to mean. You can avoid this problem completely by working in a garbage collected language (the normal approach that makes you also pay for abstraction). Or you can be much more specific about what kinds of pointers you are using, so that the compiler can reject anything that is inconsistent or just can't be proven correct as written. This is the approach of some languages like Rust.
Les types construits sont équivalents aux preuves, donc si vous écrivez un système de types qui oublie cela, alors toutes sortes de choses tournent mal. Supposons un instant que lorsque nous déclarons un type, nous voulons en fait dire que nous affirmons la vérité sur ce qui est dans la variable.
- int * x; // Une fausse affirmation. x existe et ne pointe pas vers un int
- int * y = z; // Uniquement vrai s'il est prouvé que z pointe vers un int
- * (x + 3) = 5; // Seulement vrai si (x + 3) pointe vers un int dans le même tableau que x
- int c = a / b; // Uniquement vrai si b n'est pas nul, comme: "non nul int b = ...;"
- nullable int * z = NULL; // nullable int * n'est pas la même chose qu'un int *
- int d = * z; // Une fausse assertion, car z est nullable
- if (z! = NULL) {int * e = z; } // Ok car z n'est pas nul
- gratuit (y); int w = * y; // Fausse affirmation, car y n'existe plus à w
Dans ce monde, les pointeurs ne peuvent pas être nuls. Les déréférences NullPointer n'existent pas et les pointeurs n'ont pas besoin d'être vérifiés pour la nullité n'importe où. Au lieu de cela, un "nullable int *" est un type différent qui peut avoir sa valeur extraite à null ou à un pointeur. Cela signifie qu'au point où l' hypothèse non nulle commence, vous allez enregistrer votre exception ou descendre une branche nulle.
Dans ce monde, les erreurs de tableau hors limites n'existent pas non plus. Si le compilateur ne peut pas prouver qu'il est dans des limites, essayez de réécrire pour que le compilateur puisse le prouver. Si ce n'est pas le cas, vous devrez alors introduire manuellement l'Assomption à cet endroit; le compilateur peut y trouver une contradiction ultérieurement.
De plus, si vous ne pouvez pas avoir de pointeur qui n'est pas initialisé, vous n'aurez pas de pointeurs vers la mémoire non initialisée. Si vous avez un pointeur sur la mémoire libérée, il doit être rejeté par le compilateur. Dans Rust, il existe différents types de pointeurs pour rendre ces types de preuves raisonnables. Il existe des pointeurs appartenant exclusivement (c'est-à-dire: pas d'alias), des pointeurs vers des structures profondément immuables. Le type de stockage par défaut est immuable, etc.
Il y a aussi le problème de l'application d'une grammaire bien définie sur les protocoles (qui inclut les membres de l'interface), pour limiter la surface d'entrée exactement à ce qui est prévu. La chose à propos de "l'exactitude" est: 1) Débarrassez-vous de tous les états indéfinis 2) Assurez la cohérence logique . La difficulté d'y arriver a beaucoup à voir avec l'utilisation d'un outillage extrêmement mauvais (du point de vue de l'exactitude).
C'est exactement pourquoi les deux pires pratiques sont les variables globales et les gotos. Ces choses empêchent de mettre des conditions pré / post / invariantes autour de quoi que ce soit. C'est aussi pourquoi les types sont si efficaces. Au fur et à mesure que les types se renforcent (en utilisant finalement les types dépendants pour prendre en compte la valeur réelle), ils se rapprochent d'être des preuves de correction constructives en elles-mêmes; faire échouer la compilation des programmes incohérents.
Gardez à l'esprit qu'il ne s'agit pas seulement d'erreurs stupides. Il s'agit également de défendre la base de code contre les infiltrés intelligents. Il y aura des cas où vous devrez rejeter une soumission sans une preuve convaincante générée par la machine de propriétés importantes comme "suit le protocole formellement spécifié".