Entrées/Sorties bit à bit

1 - Manipulation de bits

En C, il n'existe pas d'opérateur permettant de changer la valeur d'un bit particulier dans une variable (de toutes façons, ce genre d'instruction n'existe pas forcément dans le langage machine cible). Cependant, en utilisant judicieusement les opérateurs de décalage (<< et >>), la négation bit à bit (~) et les opérateurs logiques bit à bit (| et &), il est possible de manipuler chacun des bits d'une variable indépendamment. A titre d'exemple, l'expression suivante à la même valeur que le bit 4 de la variable toto (le bit 0 étant le bit de poids faible) :

(toto >> 4) & 1

Exercice :

2 - Réalisation de fonctions d'Entrées/Sorties bit à bit

La bibliothèque C standard offre de nombreuses fonctions d'accès à un fichier. Ce sont, entre autres, les fonctions fopen, fscanf, fprintf, ... dont les prototypes sont fournis dans stdio.h. Ces fonctions permettent d'ouvrir un ficher en lecture ou écriture puis d'y écrire ou lire des caractères, des entiers, des flottants, etc. Malheureusement, la bibliothèque standard n'offre pas de moyen de lire ou d'écrire un seul bit à la fois dans un fichier.

Le but de ce TD est d'écrire une bibliothèque permettant d'effectuer des entrées/sorties bit à bit dans un fichier. Une telle bibliothèque se révélera particulièrement utile pour implémenter des algorithmes de compression (par exemple). Le problème qui se pose lors de la réalisation de fonctions d'accès bit à bit aux fichiers est que nous devons les réaliser en nous servant des fonctions standard pour lesquelles la taille minimale d'un accès est l'octet (8 bits).

Une réalisation possible pour les fonctions d'écriture de cette bibliothèque consisterait à maintenir un ensemble de bits en attente d'écriture (dans une variable, qui nous servirait ainsi de mémoire tampon) et, dès que ces bits sont au nombre de huit, de réaliser l'écriture de l'octet correspondant à l'aide d'une des fonctions de la bibliothèque standard. Pour les fonctions de lecture, nous pouvons utiliser la même idée en maintenant un octet lu à l'avance dans le fichier duquel nous extrayons les bits les uns après les autres au fur et à mesure des demandes. La figure ci-dessous illustre cela pour l'exemple d'une écriture bit à bit avec un tampon de 1 octet :



Figure 1 - Exemple d'écriture bit à bit avec un tampon de 1 octet

Exercices :

Pour répondre à chacun de ces exercices, il est utile d'avoir une idée d'ensemble du problème que nous cherchons à résoudre. Commencez donc par lire l'ensemble des questions posées avant d'y répondre dans l'ordre.

3 - Améliorations : fin des données

La spécification précédente n'est pas très satisfaisante en ce qui concerne la fin d'un accès bit à bit. Théoriquement, nous devrions pouvoir insérer un nombre quelconque de bits (pas forcément multiple de 8) à n'importe quelle position lors de l'accès à un fichier. Le problème vient de la relecture : comment déterminer à quel moment la séquence de bits se termine alors qu'on ne peut lire que des octets, c'est-à-dire des groupes de 8 bits ?

Une première solution, un peu naïve, serait d'écrire le nombre de bits contenus dans la séquence au tout début de celle-ci. Le désavantage de cette méthode est que, pour compter la séquence de bits, elle oblige à conserver en mémoire une séquence qui peut être arbitrairement longue avant de réaliser l'écriture proprement dite dans le fichier. On peut limiter l'effet néfaste du problème en découpant la séquence en tranches (de 1024 bits par exemple). Autrement dit une séquence de bits serait alors constituée d'une séquence de tranches, chacune constituée de sa taille (entre 0 et 1024) en suivie des bits eux même.

Une autre idée serait de s'inspirer des techniques utilisées dans les réseaux (au niveau de la couche liaison de données) afin de délimiter nos séquences. L'idée est de réquisitionner une séquence de données particulière pour indiquer la présence de directives destinées à la bibliothèque de gestion d'accès bit à bit. Par exemple, nous pouvons réquisitionner l'octet 0xFF pour coder nos directives. Cet octet sera toujours suivi d'un second octet codant la directive proprement dite. Un exemple de contenu possible pour ce second octet peut être :

il convient alors, lors de l'écriture des données dans le fichier, de remplacer tout octet de valeur 0xFF par une séquence de deux octets successifs de valeur 0xFF (on recode la donnée réquisitionnée en son équivalent sous forme de directive) et lors de la lecture de traiter le cas particulier des directives.

Exercice :

4 - Test de votre implémentation

Bien entendu, une fois que vous aurez proposé une implémentation complète des accès bit à bit à un fichier, il sera nécessaire de la tester. Des tests manuels rapides permettent souvent de mettre au point une première implémentation et de repérer les plus gros bugs, mais ils sont souvent insuffisants. Pour les compléter, il faudrait idéalement tester tous les cas de séquence de bits possibles aussi bien en écriture qu'en lecture. Malheureusement, ceci n'est généralement pas possible en raison du nombre trop grand (éventuellement infini) de cas différents. A défaut, le test d'un programme repose généralement sur une ou plusieurs des techniques suivantes :