Si nous nous en remettons au livre (ou à toute autre version de la spécification du langage si vous préférez), quelle puissance de calcul une implémentation C peut-elle avoir?
Notez que «implémentation C» a une signification technique: il s'agit d'une instanciation particulière de la spécification du langage de programmation C où le comportement défini par l'implémentation est documenté. L'implémentation AC ne doit pas pouvoir fonctionner sur un ordinateur réel. Il doit implémenter le langage entier, y compris chaque objet ayant une représentation de chaîne de bits et les types ayant une taille définie par l'implémentation.
Aux fins de cette question, il n'y a pas de stockage externe. La seule entrée / sortie que vous pouvez effectuer est getchar
(pour lire l'entrée de programme) et putchar
(pour écrire la sortie de programme). De même, tout programme qui invoque un comportement non défini n'est pas valide: un programme valide doit avoir son comportement défini par la spécification C plus la description de l'implémentation des comportements définis par l'implémentation répertoriés dans l'annexe J (pour C99). Notez que l'appel de fonctions de bibliothèque non mentionnées dans la norme est un comportement non défini.
Ma première réaction a été qu'une implémentation C n'est rien de plus qu'un automate fini, car elle a une limite sur la quantité de mémoire adressable (vous ne pouvez pas adresser plus de sizeof(char*) * CHAR_BIT
bits de stockage, car les adresses de mémoire distinctes doivent avoir des modèles de bits distincts lorsqu'elles sont stockées dans un pointeur d'octets).
Cependant, je pense qu'une mise en œuvre peut faire plus que cela. Autant que je sache, la norme n'impose aucune limite à la profondeur de récursivité. Vous pouvez donc effectuer autant d'appels de fonction récursifs que vous le souhaitez, mais tous, sauf un nombre fini d'appels, doivent utiliser des register
arguments non adressables ( ). Ainsi, une implémentation C qui permet une récursion arbitraire et n'a pas de limite sur le nombre d' register
objets peut coder des automates déterministes de refoulement.
Est-ce correct? Pouvez-vous trouver une implémentation C plus puissante? Existe-t-il une implémentation C de Turing complète?
la source
Réponses:
unsigned char
unsigned char*
sizeof(unsigned char*)
sizeof(unsigned char *)
unsigned char
Un programme pourrait stocker une quantité illimitée d'informations sur la pile si rien de ce qui est alloué sur la pile n'a jamais pris son adresse ; on pourrait donc avoir un programme C capable de faire certaines choses qui ne pourraient être faites par aucun automate fini de n'importe quelle taille. Ainsi, même si (ou peut-être parce que) l'accès aux variables de pile est beaucoup plus limité que l'accès aux variables allouées dynamiquement, il transforme C d'un automate fini en automate déroulant.
la source
Avec la bibliothèque de threads C11 (en option), il est possible de réaliser une implémentation complète de Turing avec une profondeur de récursivité illimitée.
La création d'un nouveau thread produit une seconde pile; deux piles suffisent pour que Turing soit complet. Une pile représente ce qui est à gauche de la tête, l'autre pile ce qui est à droite.
la source
Je pense que c'est Turing complet : nous pouvons écrire un programme qui simule un UTM en utilisant cette astuce (j'ai rapidement écrit le code à la main donc il y a probablement des erreurs de syntaxe ... mais j'espère qu'il n'y a pas d'erreurs (majeures) dans la logique :-)
Le
head
sera un pointeur vers unecell_t
structurestacker
fonction lorsqu'elle lit le dernier symbole de la bande d'entrée (en utilisant readchar)EDIT: après y avoir réfléchi un peu, il y a un problème avec les pointeurs ...
si chaque appel de la fonction récursive
stacker
peut conserver un pointeur valide vers une variable définie localement dans l'appelant, alors tout va bien ; sinon mon algorithme ne peut pas maintenir une liste valide à double lien sur la récursion illimitée (et dans ce cas, je ne vois pas un moyen d'utiliser la récursivité pour simuler un stockage à accès aléatoire illimité).la source
stacker
newcell
stacker
sizeof(cell_t)
Tant que vous avez une taille de pile d'appels illimitée, vous pouvez encoder votre bande sur la pile d'appels et y accéder de manière aléatoire en rembobinant le pointeur de pile sans revenir des appels de fonction.EDIT : Si vous ne pouvez utiliser que le bélier, qui est fini, cette construction ne fonctionne plus, alors voyez ci-dessous.
Cependant, il est très discutable pourquoi votre pile peut être infinie, mais le bélier intrinsèque ne l'est pas.
Donc, en fait, je dirais que vous ne pouvez même pas reconnaître tous les langages réguliers, car le nombre d'états est limité (si vous ne comptez pas l'astuce de rembobinage pour exploiter la pile infinie).Je suppose même que le nombre de langues que vous pouvez reconnaître est fini (même si les langues elles-mêmes peuvent être infinies, par exemplea*
, ça va, maisb^k
ne fonctionne que pour un nombre fini dek
s).EDIT : Ce n'est pas vrai, car vous pouvez coder l'état actuel dans des fonctions supplémentaires, de sorte que vous pouvez vraiment reconnaître TOUTES les langues régulières.
Vous pouvez très probablement obtenir toutes les langues de type 2 pour la même raison, mais je ne sais pas si vous pouvez réussir à mettre les deux, l'état et la constante de pile sur la pile d'appels. Mais d'une manière générale, vous pouvez effectivement oublier le bélier, car vous pouvez toujours mettre à l'échelle la taille de l'automate afin que votre alphabet dépasse la capacité du bélier. Donc, si vous pouviez simuler une MT avec seulement une pile, Type-2 serait égal à Type-0, n'est-ce pas?
la source
J'y ai pensé une fois et j'ai décidé d'essayer d'implémenter un langage non contextuel en utilisant la sémantique attendue; l'élément clé de la mise en œuvre est la fonction suivante:
Au moins, je pense que cela fonctionne. Il se peut cependant que je commette une erreur fondamentale.
Une version fixe:
la source
it = *it
devrait être remplacé parit = * (void **) it
, sinon*it
est de typevoid
.Dans le sens de la réponse de @ supercat:
Les affirmations d'incomplétude de C semblent être centrées sur le fait que des objets distincts devraient avoir des adresses distinctes, et l'ensemble des adresses est supposé être fini. Comme l'écrit @supercat
unsigned char*
sizeof(unsigned char*)
sizeof(unsigned char*)
À ce stade, il faut vérifier que la norme C permettrait effectivement cela.
sizeof
la source
uintptr_t p = (uintptr_t)sizeof(void*)
(mettre \ oméga dans quelque chose qui contient des entiers non signés). Je ne sais pas. Nous pouvons nous en sortir en définissant le résultat comme étant 0 (ou tout autre nombre).uintptr_t
devrait être infini aussi. Attention, ce type est facultatif - mais si vous avez un nombre infini de valeurs de pointeur distinctes, ilsizeof(void*)
doit également être infini, doncsize_t
doit être infini. Mon objection à propos de la réduction modulo n'est pas si évidente cependant - elle n'entre en jeu que s'il y a un débordement, mais si vous autorisez des types infinis, ils pourraient ne jamais déborder. Mais sur la main de préhension, chaque type a une valeur minimale et maximale, ce qui, pour autant que je sache, implique qu'ilUINT_MAX+1
doit déborder.size_t
types de pointeurs comme étant ℕ ∪ {ω}. Cela supprime le problème min / max. Le problème avec la sémantique de débordement demeure. Ce qui devrait être la sémantiqueuint x = (uint)ω
n'est pas clair pour moi. Encore une fois, nous pourrions prendre au hasard 0, mais cela semble un peu moche.