Le langage C
Base de donnée: (suite)
Par :
Guilhem de Wailly (gdw@erian-concept.com)
Résumé
Dans cette série d'articles, nous présentons le langage C. Il ne s'agit pas de réécrire les ouvrages de référence donnés en annexe, mais de donner, au travers du C, une méthode de programmation basée sur la notion d'interface.
Au fur et à mesure de notre progression, nous décrirons les éléments indispensables du langage et conduirons le lecteur à consulter les ouvrages de références. Le but poursuivi est clairement de parvenir à construire des programmes de manière segmentée.
Ce mois-ci, nous continuons le mini moteur base de donnée utilisant les tables de hachages.
Erratum
Dans le paragraphe ``les indiens sont là'' d'un précédent numéro, les processeurs Intel sont dits en mode big-endian, alors que c'est le contraire, ils sont en mode little-endian.
Je vous propose le code de Frederic Raynal qui écrit aussi dans ces colonnes pour détecter le mode de fonctionnement du processeur :
/*
endian.c */
#include <stdio.h>
void
main(int argc, char **argv) {
union
{
int entier;
char octets[4]
} nb;
unsigned char o0, o1, o2, o3;
nb.entier =
atoi(argv[1]);
printf("int : %x (%d)\n",
nb.entier, nb.entier);
printf("octets : %.2x %.2x
%.2x %.2x\n",
nb.octets[0],
nb.octets[1],
nb.octets[2],
nb.octets[3]);
o0 = (nb.entier >> 24) & 0xff; //MSB
o1 = (nb.entier
>> 16) & 0xff;
o2 = (nb.entier >> 8) &
0xff;
o3 = (nb.entier ) & 0xff; //LSB
printf("msb
-> lsb : %.2x %.2x %.2x %.2x\n",
o0, o1, o2,
o3);
}
Ainsi que ces commentaires :
Ceci, outre les warnings de compilation provoque l'affichage suivant sur mon Pentium 350 :
>>uname
-am ; ./a.out 65538
Linux xxx.inria.fr 2.2.18ow1 #3 SMP Mon Jan 8
13:44:33 CET 2001 i686
unknownint : 10002 (65538)
octets :
02 00 01 00
msb -> lsb : 00 01 00 02
Et sur une Sun :
>>uname
-am; ./endian-sun 65538
SunOS continuum 4.1.4 2 sun4m
int :
10002 (65538)
octets : 00 01 00 02
msb -> lsb : 00 01 00 02
2 constatations donc :
les x86 sont en little endian (i.e. les bytes de poids faibles sont à la fin, et la lecture se fait de droite à gauche) alors que les Sparcs sont en big endian (i.e. les bytes de poids faibles sont à droite et la lecture se fait de gauche à droite),
les opérateurs de décalage sont indépendants des architectures (heureusement d'ailleurs ;-)
Je remercie aussi Serageldine Abdalla et Christiane Dridi pour leur commentaires sur le sujets.
Description
Le mois dernier, nous avons présenté réalisation d'un petit moteur base de données sans plus de prétentions que d'utiliser les tables de hachages et de montrer ce que peut être la complexité des vrais moteurs. Nous continuons cette présentation. Le code précédent et le code présenté ici sont rassemblés dans l'URL http://www.erian-concept.com/pub/donnee.tgz.
Interface
Rappelons l'interface que nous souhaitons réaliser. En rouge sont les fonctions présentées dans ces lignes :
#ifndef
___DONNEE_H
#define ___DONNEE_H
/* Type des
champs */
typedef enum {
CHAMP_DERNIER = 0,
/* Indicateur de fin */
CHAMP_BOOLEEN = 1,
CHAMP_CARACTERE = 2,
CHAMP_ENTIER = 3,
CHAMP_REEL =
4,
CHAMP_CHAINE = 5
} Type;
/* Descripteur d'un
champ d'une table */
typedef struct __Champ {
Type
type; /* Type de champ */
# define
LONGUEUR_NOM 15
char nom[LONGUEUR_NOM]; /*
Son nom */
unsigned longueur; /*
Longueur de la chaîne */
unsigned index;
/* Est-il indéxé ? */
void
* reserve_1; /* Mots réservés */
unsigned reserve_2;
} _Champ, * Champ;
/*
Descripteur d'un table */
typedef struct
__Descripteur {
unsigned reserve_3; /* mot
réservé */
# define LONGUEUR_ENTETE
100 /* Entête du fichier */
char
entete[LONGUEUR_ENTETE];
# define MAX_CHAMPS 50
_Champ champs[MAX_CHAMPS]; /* Champs de la table */
}
_Descripteur, * Descripteur;
/* Types de données
opaques */
typedef struct __Donnee *
Donnee;
typedef struct __Enreg * Enreg;
/*
Fonctions de l'interface */
/* Gestion des tables
*/
Donnee donnee_cree (char *
nom,
Descripteur);
Donnee donnee_ouvre (char *
nom);
int
donnee_ferme (Donnee);
/* Gestion des
enregistrements */
Enreg
donnee_nouveau (Donnee);
Enreg donnee_lit
(Donnee,
char
* index,
char
* valeur);
int donnee_supprime
(Donnee,
char
* index,
char
* valeur);
int donnee_relache (Enreg);
void
donnee_tous (Donnee,
char
* index,
int
(* proc) (void *
param,
Enreg),
void
* param);
/* Gestion des champs */
void *
donnee_valeur (Enreg,
char
* champ);
int donnee_change
(Enreg,
char
* champ,
void * valeur);
#endif /* ___DONNEE_H */
Table
On rappelle que la réalisation propose des indexes ``tout en mémoire'' basés sur des tables de hachage. Dans la réalité, il faudrait réaliser des indexes paginés ou seule une partie de l'index reste en mémoire.
La fonction de fermeture doit donc sauver tous les indexes associés à la table et libérer les zones de mémoire allouées :
int
donnee_ferme (Donnee donnee) {
int i;
/*
Ecriture des enregistrements modifiés et
*
libération de tous les enregistrements. */
hash_tous
(donnee->enregs, 0, _free_enreg);
/* Sauvegarde des
indexes ... */
for (i = 0;
donnee->descripteur.champs[i].type !=
CHAMP_DERNIER &&
i < MAX_CHAMPS;
i++) {
if
(donnee->descripteur.champs[i].index) {
char
nom_index[100];
/* Création du nom de l'index
*/
_fichier_index (donnee,
donnee->descripteur.champs + i,
nom_index);
/* Sauvegarde de l'index */
hash_sauve ((Hash) donnee->descripteur.champs[i].hash,
nom_index);
/* Destruction de l'index en
mémoire */
hash_detruit ((Hash)
donnee->descripteur.champs[i].hash);
}
}
/*
Destruction de tables de hachage internes. */
hash_detruit
(donnee->champs);
hash_detruit (donnee->enregs);
/* Fermeture du fichier */
close (donnee->id);
free (donnee->nom);
free (donnee);
return
(1);
}
La fonction _free_enreg écrit dans le fichier des données l'enregistrement, s'il a été modifié, et libère les ressources mémoires qu'il utilise :
/*
Libère un enregistrement */
static int
_free_enreg (void * parametre,
char
* identificateur,
void * donnee,
int taille_donnee) {
Enreg enreg
= * (Enreg *) donnee;
if (enreg->modifie) {
_ecrit_enreg (enreg);
}
free (enreg->tampon);
free (enreg);
return (1);
}
La fonction _ecrit_enreg écrit dans le fichier des données l'enregistrement. Si l'enregistrement est nouveau (lors d'une création), un nouvel emplacement est recherché ; sinon, on utilise l'emplacement initial :
/*
Ecrit un enregistrement sur disque */
static int
_ecrit_enreg (Enreg enreg) {
/* Emplacement nouveau ? */
if
(! enreg->deplacement) {
/* Recherche d'un emplacement
libre dans la
liste des emplacements libres. */
enreg->deplacement = _libre (enreg->donnee);
/* S'il
n'y a pas d'emplacement libre, l'enregistrement
est placé
à la fin du fichier. */
if (!
enreg->deplacement) {
lseek (enreg->donnee->id,
0, SEEK_END);
enreg->deplacement = lseek
(enreg->donnee->id,
0,
SEEK_END);
}
/* Ajout dans la table des enregistremenbts. */
hash_ajoute (enreg->donnee->enregs,
(char *) & enreg->deplacement,
&
enreg,
sizeof (enreg));
}
/*
On se place sur l'enregistrement. */
lseek
(enreg->donnee->id,
enreg->deplacement,
SEEK_SET);
/* On l'écrit. */
write (enreg->donnee->id,
enreg->tampon,
enreg->donnee->taille);
/* Maintenant, l'enregistrement est synchronisé
avec
le disque. */
enreg->modifie = 0;
return
(1);
}
La fonction suivante retourne le déplacement du premier enregistrement libre de la table. On rappelle que lorsqu'un enregistrement est supprimé, il n'est pas effectivement supprimé du fichier des données, mais simplement inséré dans la liste des enregistrements libres. De cette manière, l'emplacement qu'il occupe pourra être réutilisé :
/*
Retourne le déplacement du premier enregistrement
* libre
de la base
*/
static unsigned _libre (Donnee
donnee) {
unsigned libre =
donnee->descripteur.libre;
if (libre) {
lseek (donnee->id,
libre,
SEEK_SET);
read (donnee->id,
&
donnee->descripteur.libre,
sizeof
(unsigned));
lseek (donnee->id,
0,
SEEK_SET);
write (donnee->id,
& donnee->descripteur.libre,
sizeof
(unsigned));
}
return (libre);
}
Lorsqu'un trop grand nombre d'enregistrements sont libérés sans être réutilisés, une réorganisation du fichier s'impose sinon le fichier grossit inutilement. Cette réorganisation passe en général par la suppression des indexes, la réécriture du fichier des données sans tenir compte des enregistrements libres, et enfin par la réindexation du fichier des données. Cette opération est coûteuse en temps, aussi elle n'est pas automatique.
Enfin, la fonction _fichier_index retourne le nom du fichier d'index associé à un champ :
/*
Fabrique le nom du fichier d'index associé
* à un
index
*/
static char * _fichier_index (Donnee
donnee,
Champ champ,
char * tampon) {
char * str;
/* Recopie du nom dans le tampon */
strcpy (tampon,
donnee->nom);
/* recherche du point qui sépare
l'extension */
str = strchr (tampon, '.');
/* Par défaut, aller à la fin du nom */
if
(! str) str = tampon + strlen (tampon);
/* ajout
du nom de champ et de l'extension */
sprintf (str,
"-%s.idx", champ->nom);
return
(tampon);
}
Enregistrement
La fonction donnee_nouveau retourne un enregistrement vierge :
/*
Création d'un enregistrement */
Enreg donnee_nouveau
(Donnee donnee) {
/* Allocation de l'enregistrement */
Enreg enreg = (Enreg) malloc (sizeof (_Enreg));
if (! enreg) return (0);
/* Allocation de
tampon associé à l'enregistrement */
enreg->tampon = (char *) calloc (1,
donnee->taille);
if (! enreg->tampon) (
free
(enreg);
return (0);
}
/* Initialisation
*/
enreg->donnee = donnee;
enreg->deplacement
= 0;
enreg->creation = 1;
enreg->modifie = 0;
enreg->relache = 0;
/* Ecriture de
l'enregistrement */
_ecrit_enreg (enreg);
return
(enreg);
}
Le nouvel enregistrement est associé à un déplacement dans le fichier des données par l'utilisation de la fonction _ecrit_enreg.
La fonction suivante retourne un enregistrement étant donné un index identifié par son nom, et une valeur de l'index :
/*
Lecture d'un enregistrement */
Enreg donnee_lit (Donnee
donnee,
char * index,
char * valeur) {
Champ champ;
unsigned
deplacement;
/* Récupération du
champs correspondant au nom
* index */
if (hash_donne (donnee->champs,
index,
& champ,
0) !=
HASH_OK) {
fprintf (stderr,
"Erreur: champ %s introuvable\n",
index);
return (0);
}
/* Vérification
que c'est bien un index */
if (! champ->index) {
fprintf (stderr,
"Erreur: le
champ %s n'est pas un index\n",
index);
return (0);
}
/* Récupération
du déplacement de l'enregistrement
*
étant données la valeur d'index */
if
(hash_donne (champ->hash,
valeur,
& deplacement,
0) != HASH_OK) {
fprintf (stderr,
"Erreur: pas
d'enregistrement"
" avec cette valeur\n");
return (0);
}
/* Lecture de
l'enregistrement */
return (_lit_enreg (donnee,
deplacement));
}
La fonction _lit_renreg retourne un enregistrement étant donné son déplacement dans le fichier. L'enregistrement est soit retrouvé dans la table des enregistrements, soit lu à partir du fichier des données :
/*
Lit un enregistrement à partir du disque
* ou de la
mémoire.
*/
static Enreg _lit_enreg
(Donnee donnee,
unsigned deplacement) {
Enreg enreg;
/*
L'enregistrement est-il déjà chargé
* en
mémoire ? */
if (hash_donne (donnee->enregs,
(char *) & deplacement,
& enreg,
0)
!= HASH_OK) {
/* Allocation de l'enregistrement et du tampon */
enreg =
(Enreg) malloc (sizeof (_Enreg));
if (! enreg) return
(0);
enreg->tampon = (char
*)
calloc
(1, donnee->taille);
if (! enreg->tampon) {
free (enreg);
return (0);
}
/* Initialisation */
enreg->donnee = donnee;
enreg->deplacement = deplacement;
enreg->modifie =
0;
enreg->relache = 0;
/* Positionnement
dans le fichier et lecture */
lseek (donnee->id,
deplacement, SEEK_SET);
read (donnee->id,
enreg->tampon, donnee->taille);
/* Ajout dans la
table des enregistrements */
hash_ajoute (donnee->enregs,
(char *) & deplacement,
& enreg,
sizeof (enreg));
}
enreg->relache = 0;
return (enreg);
}
Le mois prochaine, nous terminerons cette présentation avec les autres fonction de manipulation des enregistrements et les fonction de manipulation des champs.
L'auteur
Guilhem de Wailly, directeur de la société Erian Concept, partenaire NetUltra (www.netultra.net) et Getek (www.getek.fr).
|
Votre interlocuteur Linux ! Support, formations, configurations, administration, développements Linux. http://www.erian-concept.com |
Références
Langage C - norme ANSI : bon ouvrage de référence
Ph.
DRIX
Masson
Programmer en C++: une bonne référence
S.C.
DEWHURST et K.T. STARK
Masson
Le Langage C: la bible!
B.W. KERNIGHAN et D.M.
RITCHIE
Masson
gcc: compilateur C du GNU, et tous les outils de
développement
http://www.gnu.org
Kdevel: un environnement KDE de programmation
C
http://samuel.cs.uni-potsdam.de/~smeier/kdevelop_new/index.html