Je ne suis pas sûr mais je pense que la réponse est non, pour des raisons plutôt subtiles. J'ai posé des questions sur l'informatique théorique il y a quelques années et je n'ai pas obtenu de réponse allant au-delà de ce que je vais présenter ici.
Dans la plupart des langages de programmation, vous pouvez simuler une machine de Turing en:
- simuler l'automate fini avec un programme utilisant une quantité de mémoire finie;
- simuler la bande avec une paire de listes chaînées d'entiers, représentant le contenu de la bande avant et après la position actuelle. Déplacer le pointeur signifie transférer la tête d'une des listes sur l'autre liste.
Une implémentation concrète exécutée sur un ordinateur manquerait de mémoire si la bande devenait trop longue, mais une implémentation idéale pourrait exécuter fidèlement le programme de la machine de Turing. Cela peut être fait avec un stylo et du papier, ou en achetant un ordinateur avec plus de mémoire et un compilateur ciblant une architecture avec plus de bits par mot, etc. si le programme manque de mémoire.
Cela ne fonctionne pas en C car il est impossible d'avoir une liste chaînée qui puisse s'allonger pour toujours: il y a toujours une limite au nombre de nœuds.
Pour expliquer pourquoi, je dois d’abord expliquer ce que l’implémentation C est. C est en fait une famille de langages de programmation. La norme ISO C (plus précisément une version spécifique de cette norme) définit (avec le niveau de formalité autorisé par l'anglais) la syntaxe et la sémantique d'une famille de langages de programmation. C a beaucoup de comportement indéfini et comportement défini par la mise en œuvre. Une «implémentation» de C codifie tout le comportement défini par l'implémentation (la liste des choses à codifier se trouve dans l'annexe J pour C99). Chaque implémentation de C est un langage de programmation séparé. Notez que la signification du mot «implémentation» est un peu étrange: ce qu’il signifie réellement est une variante de langage, il peut y avoir plusieurs programmes de compilateur différents qui implémentent la même variante de langage.
Dans une implémentation donnée de C, un octet a valeurs possibles. Toutes les données peuvent être représentées sous forme de tableau d'octets: un type a au plus
valeurs possibles: . Ce nombre varie selon les implémentations de C, mais pour une implémentation donnée de C, c'est une constante. 2 CHAR_BIT × sizeof (t)2CHAR_BITt
2CHAR_BIT × sizeof (t)
En particulier, les pointeurs peuvent uniquement prendre au plus . Cela signifie qu'il existe un nombre maximum fini d'objets adressables.2CHAR_BIT × sizeof (void *)
Les valeurs de CHAR_BIT
et sizeof(void*)
sont observables. Par conséquent, si vous manquez de mémoire, vous ne pouvez pas continuer à exécuter votre programme avec des valeurs plus grandes pour ces paramètres. Vous exécuteriez le programme sous un autre langage de programmation - une implémentation C différente.
Si les programmes dans un langage ne peuvent avoir qu'un nombre limité d'états, le langage de programmation n'est pas plus expressif que les automates finis. Le fragment de C qui est limité au stockage adressable autorise uniquement au plus états de programme où est la taille de l'arborescence de la syntaxe abstraite du fichier. programme (représentant l’état du flux de contrôle), ce programme peut donc être simulé par un automate fini comportant autant d’états. Si C est plus expressif, il doit utiliser d'autres fonctionnalités. nn × 2CHAR_BIT × sizeof (void *)n
C n'impose pas directement une profondeur de récursion maximale. Une implémentation est autorisée à avoir un maximum, mais il est également permis de ne pas en avoir. Mais comment pouvons-nous communiquer entre un appel de fonction et son parent? Les arguments ne servent à rien s'ils sont adressables, car cela limiterait indirectement la profondeur de la récursivité: si vous avez une fonction, int f(int x) { … f(…) …}
toutes les occurrences des x
trames actives f
ont leur propre adresse et le nombre d'appels imbriqués est donc limité par le nombre des adresses possibles pour x
.
Programme AC peut utiliser un stockage non adressable sous la forme de register
variables. Les implémentations «normales» ne peuvent avoir qu'un petit nombre fini de variables qui n'ont pas d'adresse, mais en théorie, une implémentation pourrait permettre une quantité de register
stockage illimitée . Dans une telle implémentation, vous pouvez effectuer un nombre illimité d'appels récursifs à une fonction, tant que ses arguments le sont register
. Mais comme les arguments sont register
, vous ne pouvez pas leur faire un pointeur et vous devez donc copier leurs données explicitement: vous ne pouvez transmettre qu'une quantité finie de données, pas une structure de données de taille arbitraire composée de pointeurs.
Avec une profondeur de récursion non limitée et la restriction selon laquelle une fonction ne peut obtenir des données que de son appelant direct ( register
arguments) et renvoyer des données à son appelant direct (la valeur de retour de la fonction), vous bénéficiez de la puissance des automates déterministes .
Je ne peux pas trouver un moyen d'aller plus loin.
(Vous pouvez bien sûr faire en sorte que le programme stocke le contenu de la bande en externe, par le biais de fonctions d’entrée / sortie dans un fichier. Mais vous ne voudriez pas demander si C est Turing-complete, mais si C plus un système de stockage infini est Turing-complete . laquelle la réponse est ennuyeux « oui » Vous pourriez aussi bien définir le stockage d'être un oracle de Turing - appel fopen("oracle", "r+")
, fwrite
le contenu de la bande initiale et fread
. retour le contenu de la bande finale)