Non sono sicuro ma penso che la risposta sia no, per ragioni piuttosto sottili. Ho chiesto a Teorica Informatica alcuni anni fa e non ho avuto una risposta che vada oltre ciò che presenterò qui.
Nella maggior parte dei linguaggi di programmazione, è possibile simulare una macchina Turing:
- simulare l'automa finito con un programma che utilizza una quantità finita di memoria;
- simulando il nastro con una coppia di liste collegate di numeri interi, che rappresentano il contenuto del nastro prima e dopo la posizione corrente. Spostare il puntatore significa trasferire la testa di una delle liste sull'altra lista.
Un'implementazione concreta in esecuzione su un computer esaurirebbe la memoria se il nastro fosse troppo lungo, ma un'implementazione ideale potrebbe eseguire fedelmente il programma della macchina di Turing. Questo può essere fatto con carta e penna, o acquistando un computer con più memoria e un compilatore indirizzato a un'architettura con più bit per parola e così via se il programma esaurisce la memoria.
Questo non funziona in C perché è impossibile avere un elenco collegato che può crescere per sempre: c'è sempre un limite al numero di nodi.
Per spiegare perché, devo prima spiegare cos'è un'implementazione in C. C è in realtà una famiglia di linguaggi di programmazione. Lo standard ISO C (più precisamente, una versione specifica di questo standard) definisce (con il livello di formalità che l'inglese consente) la sintassi e la semantica una famiglia di linguaggi di programmazione. C ha molti comportamenti indefiniti e comportamenti definiti dall'implementazione. Una "implementazione" di C codifica tutto il comportamento definito dall'implementazione (l'elenco delle cose da codificare è nell'appendice J per C99). Ogni implementazione di C è un linguaggio di programmazione separato. Si noti che il significato della parola "implementazione" è un po 'peculiare: ciò che significa in realtà è una variante di lingua, possono esserci più programmi di compilazione diversi che implementano la stessa variante di lingua.
In una data implementazione di C, un byte ha valori possibili. Tutti i dati possono essere rappresentati come una matrice di byte: un tipo ha al massimo
possibili valori. Questo numero varia in diverse implementazioni di C, ma per una data implementazione di C, è una costante.2CHAR_BITt
2CHAR_BIT×sizeof(t)
In particolare, i puntatori possono assumere al massimo . Ciò significa che esiste un numero massimo finito di oggetti indirizzabili.2CHAR_BIT×sizeof(void*)
I valori di CHAR_BIT
e sizeof(void*)
sono osservabili, quindi se si esaurisce la memoria, non è possibile riprendere a eseguire il programma con valori maggiori per tali parametri. Eseguiresti il programma con un linguaggio di programmazione diverso - un'implementazione C diversa.
Se i programmi in una lingua possono avere solo un numero limitato di stati, il linguaggio di programmazione non è più espressivo degli automi finiti. Il frammento di C che è limitato alla memoria indirizzabile consente al massimo indica dove è la dimensione dell'albero di sintassi astratto della programma (che rappresenta lo stato del flusso di controllo), quindi questo programma può essere simulato da un automa finito con molti stati. Se C è più espressivo, deve essere attraverso l'uso di altre funzionalità. nn×2CHAR_BIT×sizeof(void*)n
C non impone direttamente una profondità massima di ricorsione. Un'implementazione può avere un massimo, ma può anche non averne uno. Ma come possiamo comunicare tra una chiamata di funzione e il suo genitore? Gli argomenti non sono utili se sono indirizzabili, perché ciò limiterebbe indirettamente la profondità della ricorsione: se si dispone di una funzione, int f(int x) { … f(…) …}
tutte le occorrenze x
su frame attivi di f
hanno il proprio indirizzo e quindi il numero di chiamate nidificate è limitato dal numero di possibili indirizzi per x
.
Il programma CA può utilizzare la memorizzazione non indirizzabile sotto forma di register
variabili. Le implementazioni "normali" possono avere solo un numero limitato e limitato di variabili che non hanno un indirizzo, ma in teoria un'implementazione potrebbe consentire una quantità illimitata di register
spazio di archiviazione. In una tale implementazione, è possibile effettuare una quantità illimitata di chiamate ricorsive a una funzione, purché lo siano i suoi argomenti register
. Ma dal momento che gli argomenti sono register
, non è possibile indicarli e quindi è necessario copiare i loro dati in modo esplicito: è possibile passare solo una quantità finita di dati, non una struttura di dati di dimensioni arbitrarie fatta di puntatori.
Con la profondità di ricorsione illimitata e la restrizione che una funzione può ottenere solo i dati dal suo chiamante diretto ( register
argomenti) e restituire i dati al suo chiamante diretto (il valore di ritorno della funzione), si ottiene la potenza di automi pushdown deterministici .
Non riesco a trovare un modo per andare oltre.
(Naturalmente è possibile fare in modo che il programma memorizzi il contenuto del nastro esternamente, tramite le funzioni di input / output del file. Ma non si chiederà se C è Turing completo, ma se C più un sistema di archiviazione infinito è Turing completo, per che la risposta è un noioso "sì". Si potrebbe anche definire l'archiviazione come un oracolo di Turing - chiamata fopen("oracle", "r+")
, fwrite
il contenuto del nastro iniziale e fread
indietro il contenuto del nastro finale.)