Archives de catégorie : C++C

C et C++ TP2

Par O. Bailleux, Maître de conférences HDR à l'université de Bourgogne.

TP2

Le but de ce TP est de réaliser un simulateur de machine de Turing en langage C.

1. La machine de Turing à réaliser

La machine que nous allons simuler est constituée :

  • d’une bande comportant des cases contenant des symboles,
  • d’une variable d’état contenant un entier positif ou nul,
  • d’une tête de lecture / écriture qui se situe en face d’une des cases de la bande,
  • d’un programme.

La bande comporte 32 cases. Chaque case contient un symbole parmi trois possibles : #, 0 et 1. Le symbole # a pour rôle de délimiter les données placées sur la bande. Ces données sont codées avec les symboles 0 et 1.

La variable d’état peut prendre une valeur entre 0 et 7. L’état 0 est appelé état initial. C’est l’état de la machine au début de l’exécution d’un programme. L’état 7 est appelé état final. On le note STOP Il indique la fin de l’exécution du programme de la machine.

La tête est représentée par une variable dont la valeur, comprise entre 0 et 31, représente la position sur la bande.

Voici une représentation schématique de la machine juste avant l’exécution de son programme. Sa tête de lecture est en position 0 (visualisée par le point sous la première case). Son état est 0. Sa bande contient des valeurs qui représentent la donnée d’entrée du programme. Le programme n’apparait pas sur le dessin.

image-20220116152617550

On appelle situation le couple $(v,e)$ où $v$ est la valeur de la case désignée par la tête et $e$ l’état courant de la machine. La machine représentée ci-dessus est dans la situation (valeur : #, état : 0).

Le programme associe une action à chaque situation. Une action indique ce que doit faire la machine à la prochaine étape d’exécution du programme. Elle consiste en trois ordres :

  • Une valeur à écrire sur la bande à l’emplacement actuel de la tête (qui peut être identique ou différente de la valeur actuelle).
  • Un sens de déplacement de la tête : gauche ou droite. Le déplacement sera réalisé après l’écriture de la valeur.
  • La valeur du nouvel état de de la machine (qui peut être identique ou différente de celle de l’état actuel)

Voici un exemple de programme…

Situation Action
état = 0, valeur = # #, droite, 1
état = 0, valeur = 0 0, droite, STOP
état = 0, valeur = 1 1, droite, STOP
état = 1, valeur = # #, gauche, 2
état = 1, valeur = 0 0, droite, 1
état = 1, valeur = 1 1, droite, 1
état = 2, valeur = # #, droite, STOP
état = 2, valeur = 0 1, gauche, 3
état = 2, valeur = 1 0, gauche, 2
état = 3, valeur = # #, droite, STOP
état = 3, valeur = 0 0, gauche, 3
état = 3, valeur = 1 1, gauche, 3

…qui peut être représenté graphiquement comme ci-dessous :

image-20220117150716016

Pour exécuter ce programme, on regarde la situation actuelle, qui est (valeur : #, état : 0). D’après le programme, l’action à réaliser qui est #, droite, 1. La valeur # est placée dans la case désignée par la tête, qui contenait déjà cette valeur, le nouvel état est 1 et la tête se déplace à droite.

image-20220116162414446

On regarde la nouvelle situation, (valeur : 0, état : 1). D’après le programme, l’action à réaliser est 0, droite, 1.

image-20220116163234076

Continuez à exécuter le programme à la main. Vous devriez obtenir la donnée #0100# en début de bande lorsque l’état final est atteint.

2. Conception d’un programme de simulation

Nous sommes face à un problème d’ingénierie logicielle. Nous devons réaliser un programme qui simule la machine de Turing présentée dans le section précédente. La première étape consiste à déterminer comment la machine et son programme seront représentés. Ensuite, il faudra implémenter les fonctions permettant de simuler l’exécution du programme.

Représentation de la machine

Dans le cadre de ce TP, nous utiliseront la représentation suivante de la machine sans son programme :

  • La bande est représentée par un mot binaire de 64 bits placé dans une variable globale de type uint64_t.
  • La position de la tête est représentée par une variable de type int.
  • L’état est représenté par une variable de type int.
uint64_t Tape = 0;
int Head = 0;
int State = 0;

Chaque case de la bande est représentée par 2 bits consécutifs avec la convention suivante :

  • 00 représente la valeur 0,
  • 01 représente la valeur 1,
  • 10 représente la valeur #.

Voici la manière d’initialiser la bande avec l’exemple présenté dans la section précédente.

Tape = 0b1000000101100000000000000000000000000000000000000000000000000000;
//        # 0 0 1 1 # 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 

image-20220116173408523

Il est crucial, en ingénierie logicielle, de bien distinguer les données représentées et les représentations de ces données utilisées par un programme. Ici, par exemple, la valeur 0 d’une case de la bande est représentée par la séquence de deux bits 00, la valeur 1 est représentée par la séquence 01, la valeur # par la séquence 10.

Représentation du programme

Représentation d’une action

Les actions sont représentées par des octets (type uint8_t) avec la convention suivantes :

  • Les bits de rangs 0 à 2 représentent le nouvel état.
  • Le bit de rang 3 représente le sens de déplacement de la tête (1 pour droite, 0 pour gauche).
  • Les bits de rangs 4 et 5 représentent la valeur à écrire sur la bande.
  • Les bits de rangs 6 et 7 ne sont pas utilisés.

À titre d’exemple, voici le codage de l’action #, droite, 3 :

image-20220116175112134

Pour faciliter le codage d’une action, on utilise les masques suivants, définis sous forme de constantes :

#define BLANK 0b100000 // Valeur #
#define ZERO 0b000000 // Valeur 0
#define ONE 0b010000 // Valeur 1

#define LEFT 0b000000 // Déplacement à gauche
#define RIGHT 0b001000 // Déplacement à droite

Par exemple, l’action #, droite, 3 se code :

uint8_t action = BLANK | RIGHT | 3;

Vous pouvez le vérifier en combinant les masques à l’aide du ou bitwise |.

Exercice

Essayez de coder de cette manières chacune des actions suivantes et donnez le mots binaire correspondant.

  1. 1, gauche, 2
  2. 0, droite, 4
"Réponse1"

uint8_t action = ONE | LEFT | 2; // 00010010

"Réponse2"

uint8_t action = ZERO | RIGHT | 4; // 00001100

Représentation du programme de la machine

Le programme de la machine de Turing est représenté par un tableau d’actions. Voici le programme d’exemple donné dans la section 1 :

uint8_t Prog[] =
{
ZERO|RIGHT|STOP, ONE|RIGHT|STOP, BLANK|RIGHT|1, // Si état 0
ZERO|RIGHT|1, ONE|RIGHT|1, BLANK|LEFT|2, // Si état 1
ONE|LEFT|3, ZERO|LEFT|2, BLANK|LEFT|STOP, // Si état 2
ZERO|LEFT|3, ONE|LEFT|3, BLANK|LEFT|STOP // Si état 3
};

Les trois premières actions du tableau sont celles à réaliser dans les situations ou l’état est 0 et la valeur lue sur la bande respectivement 0, 1 et #.

Les trois suivantes sont celles à réaliser dans les situations ou l’état est 1 et la valeur lue sur la bande respectivement 0, 1 et #.

Etc.

D’une manière plus générale, si l’état courant est etat et la valeur lue sur la bande est val, alors l’action à réaliser est Prog[(3 * etat) + val].

Exemple : Si la situation est (valeur : 0, état : 1), alors l’action à réaliser est Prog[3], c’est à dire ZERO|RIGHT|1 : écriture d’un 0 sur la bande, déplacement à droite, le nouvel état est 1.

Exercice

Quelle est l’action à réaliser, d’après le programme d’exemple, si la situation est (valeur : 1, état : 2) ?

"Réponse"

L’action est Prog[7], c’est à dire ZERO|LEFT|2 : écriture d’un 0, déplacement à gauche, le nouvel état est 2.

Constantes et variables globales

Voici le début du programme de simulation avec les constantes et variables globales que nous avons déjà présentées, plus la constante STOP qui représente la valeur de l’état final de la machine (celui qui termine l’exécution du programme).

#include
#include
#include

#define BLANK 0b100000
#define ZERO 0b000000
#define ONE 0b010000

#define LEFT 0b0000
#define RIGHT 0b1000

#define STOP 7

int Head = 0;
uint64_t Tape = 0;
int State = 0;
uint8_t Prog[] =
{
ZERO|RIGHT|STOP, ONE|RIGHT|STOP, BLANK|RIGHT|1, // Si état 0
ZERO|RIGHT|1, ONE|RIGHT|1, BLANK|LEFT|2, // Si état 1
ONE|LEFT|3, ZERO|LEFT|2, BLANK|LEFT|STOP, // Si état 2
ZERO|LEFT|3, ONE|LEFT|3, BLANK|LEFT|STOP // Si état 3
};

Fonctions à définir

Lecture d’une valeur sur la bande

int read(int i);

Lit la valeur située en position i sur la bande. La valeur de retour peut être 0, 1 ou 2 (qui représente la valeur #).

Affichage

void print();

Affiche la bande, la tête de lecture et l’état courant. Avec les valeurs des variables données précédemment, les lignes…

Tape = 0b1000000101100000000000000000000000000000000000000000000000000000;
print();

…doivent produire l’affichage suivant :

# 0 0 1 1 # 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 [0]
T

La lettre T représente la position de la tête (ici au début de la bande). La valeur entre crochet est celle de l’état courant, donnée par la variable State.

Cette fonction appelle la fonction read.

Écriture d’une valeur sur la bande

void write(int i, int val);

Écrit la valeur val (0, 1 ou 2) en position i sur la bande. La valeur 2 se représente aussi 0b10 et correspond à la valeur notée #.

Exécution du programme

uint8_t nextAction();

Retourne la prochaine action à réaliser qui dépend :

  • de l’état courant (variable State),
  • de la valeur désignée par la tête de lecture sur la bande
  • et du programme.
void operate(uint8_t action);

Réalise l’action désignée par le paramètre action. Cette fonction écrit une valeur sur la bande, modifie la position de la tête (variable Head), et met à jour la valeur de l’état courant (variable State).

Si la tête de lecture est en début de bande et si l’action suppose un déplacement à gauche alors la tête doit garder sa position. Si la tête est en fin de bande et si l’action impose un déplacement à droite alors la tête doit garder sa position.

Ces deux fonctions sont appelées par la fonction run, définie ci-dessous, qui assure l’exécution du programme.

void run()
{
print();
State = 0;
while(State != END_STATE)
{
uint8_t action = nextAction();
operate(action);
print();
getchar(); // Il faut presser la touche entrée pour continuer
}
printf("Exécution terminée\n");
}

Travail à réaliser

Vous devez définir les fonctions dont le code n’a pas été donné précédemment, les tester séparément, puis tester l’exécution du programme d’exemple. Ce programme réalise une incrémentation de la valeur en base 2 située en début de bande entre les 2 symboles #.

Modifications du simulateur

À ce stade, vous devez avoir un simulateur fonctionnel et comprendre les moindres détails de son implémentation et des représentations des données utilisées. Les modifications à réaliser devront être testées avec des programmes de machine de Turing conçus à cette fin.

Augmentation du nombre d’états

La machine simulée précédemment peut admettre au plus 7 états, numérotés de 0 à 6, plus l’état final qui arrête le programme.

Vous devez modifier le simulateur, en réutilisant au maximum le code existant et les principes et représentation utilisés, pour que le nombre maximum d’états soit égal à 32 (états 0 à 30 plus l’état final codé par l’entier 31). Dans la nouvelle version, tous les bits des octets représentant les actions seront utilisés, ce qui permettra de représenter les états sur 5 bits.

Augmentation de la taille de la bande

La machine simulée précédemment a une bande de 32 cases représentée par un seul entier de type uint64_t.

Vous devez réaliser une version dans laquelle la taille de la bande, multiple de 32, est définie par une constante SIZE_TAPE. Le nombre de cases de la bande sera égal à SIZE_TAP * 32 et la bande sera représentée par le tableau :

uint64_t Tape[SIZE_TAPE] = {0};

Le codage du programme de la machine de Turing simulée doit être identique et vous devez réutiliser autant que possible les principes et la structures du simulateur précédent en modifiant certaines fonctions.

L’affichage doit être modifié de manière à que la tête apparaisse au milieu de l’écran et que seules les parties de la bande située autour, dans la limite de 16 cases de chaque coté, soient affichées.

Révisions de C et C++

Langage C

Exercice 1

Solution

Exercice 2

Solution

Introduction aux exercices suivants

Exercice 3

Solution

Exercice 4

Indice

Solution

Exercice 5

Solution

Introduction aux exercices suivants

Exercice 6

Indice

Solution

Exercice 7

Indice

Solution

Langage C++

Exercice 8

Solution

Exercice 9

Solution

Exercice 10

Indice 1

Solution