• Aucun résultat trouvé

4.1 - Une petite bibliothèque dans le style C

Une bibliothèque commence habituellement comme une collection de fonctions, mais si vous avez utilisé des bibliothèques C écrites par autrui, vous savez qu'il s'agit généralement de plus que cela, parce que la vie ne se limite pas à des comportements, des actions et des fonctions. On y trouve également des caractéristiques (bleu, livres, texture, luminance), qui sont représentées par des données. Et lorsque vous commencez à travailler avec un ensemble de caractéristiques en C, il est très pratique de les rassembler dans une structure, particulièrement si vous désirez représenter plus d'un objet similaire dans l'espace de votre problème. De cette manière, vous pouvez définir une variable du type de cette structure pour chaque objet.

Ainsi, la plupart des bibliothèques C se composent d'un ensemble de structures et d'un ensemble de fonctions qui agissent sur ces structures. Comme exemple de ce à quoi un tel système peut ressembler, considérez un objet qui se comporte comme un tableau, mais dont la taille peut être établie à l'exécution, lors de sa création. Je l'appellerai CSTash. Bien qu'il soit écrit en C++, il utilise un style qui correspond à ce que vous écririez en C:

//: C04:CLib.h

// Fichier d'en-tête pour une bibliothèque // écrite dans le style C

// Un entité semblable à un tableau créée à l'exécution typedef struct CStashTag {

int size; // Taille de chaque espace int quantity; // Nombre d'espaces de stockage int next; // Prochain espace libre // Tableau d'octets alloué dynamiquement:

unsigned char* storage;

} CStash;

void initialize(CStash* s, int size);

void cleanup(CStash* s);

int add(CStash* s, const void* element);

void* fetch(CStash* s, int index);

int count(CStash* s);

void inflate(CStash* s, int increase);

///:~

Un nom tel que CStashTagest généralement employé pour une structure au cas où vous auriez besoin de référencer cette structure à l'intérieur d'elle-même. Par exemple, lors de la création d'une liste chaînée(chaque élément dans votre liste contient un pointeur vers l'élément suivant), vous avez besoin d'un pointeur sur la prochaine variable struct, vous avez donc besoin d'un moyen d'identifier le type de ce pointeur au sein du corps même de la structure. Aussi, vous verrez de manière presque universelle le mot clé typedefutilisé comme ci-dessus pour chaque structprésente dans une bibliothèque C. Les choses sont faites de cette manière afin que vous puissiez traîter une structure comme s'il s'agissait d'un nouveau type et définir des variables du type de cette structure de la manière suivante:

CStash A, B, C;

Le pointeur storageest de type unsigned char*. Un unsigned charest la plus petite unité de stockage que supporte un compilateur C, bien que, sur certaines machines, il puisse être de la même taille que la plus grande.

Cette taille dépend de l'implémentation, mais est souvent de un octet. Vous pourriez penser que puisque CStashest conçu pour contenir n'importe quel type de variable, void*serait plus approprié. Toutefois, l'idée n'est pas ici de traîter cet espace de stockage comme un bloc d'un type quelconque inconnu, mais comme un bloc contigu d'octets.

Le code source du fichier d'implémentation (que vous n'obtiendrez pas si vous achetez une bibliothèque commerciale - vous recevrez seulement un obj, ou un lib, ou un dll, etc. compilé) ressemble à cela:

//: C04:CLib.cpp {O}

// Implantation de l'exemple de bibliothèque écrite // dans le style C

// Declaration de la structure et des fonctions:

#include "CLib.h"

#include <iostream>

#include <cassert>

using namespace std;

// Quantité d'éléments à ajouter

// lorsqu'on augmente l'espace de stockage:

const int increment = 100;

void initialize(CStash* s, int sz) { s->size = sz;

s->quantity = 0;

s->storage = 0;

s->next = 0;

}

int add(CStash* s, const void* element) {

if(s->next >= s->quantity) //Il reste suffisamment d'espace?

inflate(s, increment);

// Copie l'élément dans l'espace de stockage, // en commençant au prochain espace vide:

int startBytes = s->next * s->size;

unsigned char* e = (unsigned char*)element;

for(int i = 0; i < s->size; i++) s->storage[startBytes + i] = e[i];

s->next++;

return(s->next - 1); // Numéro de l'indice }

void* fetch(CStash* s, int index) {

// Vérifie les valeurs limites de l'indice:

assert(0 <= index);

if(index >= s->next)

return 0; // Pour indiquer la fin

// Produit un pointer sur l'élément désiré:

return &(s->storage[index * s->size]);

}

int count(CStash* s) {

return s->next; // Eléments dans CStash }

void inflate(CStash* s, int increase) { assert(increase > 0);

int newQuantity = s->quantity + increase;

int newBytes = newQuantity * s->size;

int oldBytes = s->quantity * s->size;

unsigned char* b = new unsigned char[newBytes];

for(int i = 0; i < oldBytes; i++)

b[i] = s->storage[i]; // Copie l'ancien espace vers le nouveau delete [](s->storage); // Ancien espace

s->storage = b; // Pointe sur le nouvel espace mémoire s->quantity = newQuantity;

}

void cleanup(CStash* s) { if(s->storage != 0) {

cout << "freeing storage" << endl;

delete []s->storage;

} } ///:~

initialize()effectue le travail d'initialisation pour la structure CStashen fixant les variables internes à une valeur appropriée. Initialement, le pointeur storageest mis à zéro - aucun espace de stockage n'est alloué.

La fonction add()insère un élément dans le CStashà la prochaine position disponible. D'abord, elle contrôle si il reste de l'espace à disposition. Si ce n'est pas le cas, elle étend l'espace de stockage en utilisant la fonction inflate(), décrite plus loin.

Parce que le compilateur ne connaît pas le type spécifique de la variable stockée (tout ce que la fonction reçoit est un void*), vous ne pouvez pas simplement faire une affectation, ce qui serait certainement chose pratique. A la place, vous devez copier la variable octet par octet. La manière la plus évidente de réaliser cette copie est par itération sur les indices d'un tableau. Typiquement, storagecontient déjà des octets de données, ce qui est indiqué par la valeur de next. Afin de démarrer avec le décalage d'octets approprié, nextest multiplié par la taille de chaque élément (en octets) de manière à produire startBytes. Puis, l'argument elementest converti en un unsigned char*de façon telle qu'il peut être adressé octet par octet et copié dans l'espace de stockage disponible.

nextest incrémenté de manière à indiquer le prochain emplacement disponible, et l'"indice" où la valeur a été placée afin de pouvoir récupérer cette dernière en utilisant cet indice avec fetch().

fetch()vérifie que l'indice n'est pas en dehors des limites et retourne l'adresse de la variable désirée, calculée à l'aide de l'argument index. Puisque indexreprésente le nombre d'éléments à décaler dans CStash, il doit être multiplié par le nombre d'octets occupés par chaque entité pour produire le décalage numérique en octets. Lorsque ce décalage est utilisé pour accéder à un élément de storageen utilisant l'indiçage d'un tableau, vous n'obtenez pas l'adresse, mais au lieu de cela l'octet stocké à cette adresse. Pour produire l'adresse, vous devez utiliser

l'opérateur adresse-de &.

count()peut au premier abord apparaître un peu étrange au programmeur C expérimenté. Cela ressemble à une complication inutile pour faire quelque chose qu'il serait probablement bien plus facile de faire à la main. Si vous avez une structure CStashappelée intStash, par exemple, il semble bien plus évident de retrouver le nombre de ses éléments en appelant inStash.nextplutôt qu'en faisant un appel de fonction (qui entraîne un surcoût), tel que count(&intStash). Toutefois, si vous désirez changer la représentation interne de CStash, et ainsi la manière dont le compte est calculé, l'appel de fonction apporte la flexibilité nécessaire. Mais hélas, la plupart des programmeurs ne s'ennuieront pas à se documenter sur la conception "améliorée" de votre bibliothèque. Ils regarderont la structure et prendront directement la valeur de next, et peut-être même qu'ils modifieront nextsans votre permission. Si seulement il y avait un moyen pour le concepteur de bibliothèque d'obtenir un meilleur contrôle sur de telles opérations! (Oui, c'est un présage.)