La réponse intuitive est que si vous n'avez pas de boucles illimitées et que vous n'avez pas de récursivité et que vous n'avez pas goto, vos programmes s'arrêtent. Ce n'est pas tout à fait vrai, il existe d'autres façons de dissimuler la non-terminaison, mais c'est assez bon pour la plupart des cas pratiques. Bien sûr, l'inverse est faux, il existe des langages avec ces constructions qui n'autorisent pas les programmes sans terminaison, mais ils utilisent d'autres types de restrictions telles que les systèmes de type sophistiqués.
Récursivité
Une restriction courante dans les langages de script est d'empêcher dynamiquement la récursivité: si A appelle B appelle C appelle ... appelle A, alors l'interpréteur (ou le vérificateur, dans votre cas) abandonne ou signale une erreur, même si la récursivité peut effectivement se terminer. Deux exemples concrets:
Le préprocesseur C laisse une macro intacte lors de son expansion. L'utilisation la plus courante consiste à définir un wrapper autour d'une fonction:
#define f(x) (printf("calling f(%d)\n", (x)), f(x))
f(3);
Cela s'étend à
(printf("calling f(%d)\n", (3)), f(3))
La récursion mutuelle est également gérée. Une conséquence est que le préprocesseur C se termine toujours, bien qu'il soit possible de créer des macros avec une complexité d'exécution élevée.
#define f0(x) x(x)x(x)
#define f1(x) f0(f0(x))
#define f2(x) f1(f1(x))
#define f3(x) f2(f2(x))
f3(x)
Les shells Unix développent les alias de manière récursive, mais uniquement jusqu'à ce qu'ils rencontrent un alias déjà développé. Encore une fois, l'objectif principal est de définir un alias pour une commande portant le même nom.
alias ls='ls --color'
alias ll='ls -l'
Une généralisation évidente est de permettre une profondeur de récursivité allant jusqu'à , pouvant être configurable.nn
Il existe des techniques plus générales pour prouver que les appels récursifs se terminent, comme la recherche d'un entier positif qui diminue toujours d'un appel récursif au suivant, mais ceux-ci sont considérablement plus difficiles à détecter. Ils sont souvent difficiles à vérifier, et encore moins à déduire.
Boucles
Les boucles se terminent si vous pouvez limiter le nombre d'itérations. Le critère le plus courant est que si vous avez une for
boucle (sans astuces, c'est-à-dire qui compte vraiment de à ), elle effectue un nombre fini d'itérations. Donc, si le corps de la boucle se termine, la boucle elle-même se termine.mn
En particulier, avec les boucles for (plus les constructions de langage raisonnables telles que les conditionnelles), vous pouvez écrire toutes les fonctions récursives primitives , et vice versa. Vous pouvez reconnaître syntaxiquement les fonctions récursives primitives (si elles sont écrites de manière non obscurcie), car elles n'utilisent pas de boucle while ou goto ou récursivité ou autre astuce. Les fonctions récursives primitives sont assurées de se terminer, et la plupart des tâches pratiques ne vont pas au-delà de la récursivité primitive.