Intuicyjna odpowiedź jest taka, że jeśli nie masz nieograniczonych pętli i nie masz rekurencji i nie masz goto, twoje programy zostaną zakończone. Nie jest to do końca prawdą, istnieją inne sposoby na ukrycie braku wypowiedzenia, ale wystarcza w większości praktycznych przypadków. Oczywiście odwrotność jest błędna, istnieją języki z tymi konstrukcjami, które nie pozwalają na nie kończące się programy, ale używają innych rodzajów ograniczeń, takich jak wyrafinowane systemy typów.
Rekurencja
Częstym ograniczeniem w językach skryptowych jest dynamiczne zapobieganie rekurencji: jeśli A wywołuje B wywołuje C wywołuje… wywołuje A, wówczas interpreter (lub w twoim przypadku moduł sprawdzający) rezygnuje lub sygnalizuje błąd, nawet jeśli rekursja faktycznie się zakończy. Dwa konkretne przykłady:
Preprocesor C pozostawia makro nienaruszone podczas jego rozwijania. Najczęstszym zastosowaniem jest zdefiniowanie opakowania wokół funkcji:
#define f(x) (printf("calling f(%d)\n", (x)), f(x))
f(3);
To rozwija się do
(printf("calling f(%d)\n", (3)), f(3))
Obsługiwana jest również wzajemna rekurencja. Konsekwencją jest to, że preprocesor C zawsze kończy działanie, chociaż możliwe jest budowanie makr o dużej złożoności w czasie wykonywania.
#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)
Powłoki uniksowe rozwijają aliasy rekurencyjnie, ale tylko do momentu napotkania aliasu, który jest już rozwijany. Ponownie głównym celem jest zdefiniowanie aliasu dla polecenia o podobnej nazwie.
alias ls='ls --color'
alias ll='ls -l'
Oczywistym uogólnieniem jest umożliwienie głębokości rekurencji do , przy czym może być konfigurowalne.nn
Istnieją bardziej ogólne techniki, aby udowodnić zakończenie połączeń rekurencyjnych, takie jak znalezienie pewnej dodatniej liczby całkowitej, która zawsze zmniejsza się z jednego połączenia rekurencyjnego do drugiego, ale są one znacznie trudniejsze do wykrycia. Często są trudne do zweryfikowania, nie mówiąc już o wnioskach.
Pętle
Pętle się kończą, jeśli można ograniczyć liczbę iteracji. Najczęstszym kryterium jest to, że jeśli masz for
pętlę (bez lew, tj. Taką , która naprawdę liczy się od do ), wykonuje skończoną liczbę iteracji. Więc jeśli ciało pętli się kończy, sama pętla się kończy.mn
W szczególności w przypadku pętli (oraz rozsądnych konstrukcji języka, takich jak warunkowe), możesz pisać wszystkie prymitywne funkcje rekurencyjne i odwrotnie. Prymitywne funkcje rekurencyjne można rozpoznać składniowo (jeśli są napisane w sposób nieudokumentowany), ponieważ nie używają pętli while ani goto, rekurencji lub innej sztuczki. Pierwotne funkcje rekurencyjne mają zagwarantowane zakończenie, a większość praktycznych zadań nie wykracza poza prymitywną rekurencję.