II. Les chaînes à zéro terminal▲
Les chaînes à zéro terminal (AZT en français, ASCIIZ en anglais) sont la norme dans l'API Windows.
Comme vous allez en déguster à toutes les sauces tout au long du tutoriel, il est essentiel que vous en maîtrisiez la représentation en mémoire ainsi que les subtilités du langage Pascal qui les concernent.
Une bonne approche de la problématique des chaînes à zéro terminal est déjà à votre disposition : Les Strings et pChar en Delphi par Adrien Reboisson.
II-A. Tableaux de caractères et type pChar▲
Une chaîne AZT est un tableau (à 1 dimension, un vecteur donc) de caractères terminé par 0.
Le 0 terminal est le caractère #0 (dont le code ANSI vaut 0), à ne pas confondre avec le caractère '0' (dont le code ANSI vaut 48) !
Voici la représentation d'une chaîne AZT en mémoire :
|'B'|'o'|'n'|'j'|'o'|'u'|'r'| 0 |
Théoriquement, une chaîne AZT n'a aucune limitation de taille, si ce n'est celle de la mémoire du système.
Sa déclaration est celle d'un tableau de caractères :
Var
Chaine : Array
[0
..10
] of
Char
;
La variable Chaine permet de stocker 10 caractères + le zéro terminal.
En C, il n'y a aucune différence entre un tableau en mémoire et un pointeur vers ce tableau. En Pascal, si ! Sauf pour les chaînes AZT.
Un type particulier de pointeur, pChar, permet l'adressage des chaînes AZT et le passage de chaînes AZT comme paramètres aux fonctions de l'API Windows.
1ère notion à retenir : Tableau de caractères et pointeur sur un tableau de caractères sont équivalents.
Il est donc tout-à -fait correct d'écrire les deux instructions suivantes :
Var
Chaine : Array
[0
..10
] of
Char
;
pChaine : pChar;
Begin
(* Les 2 instructions suivantes font la même chose : *)
pChaine := Chaine;
pChaine := @Chaine;
End
.
Ces deux instructions font exactement la même chose : assigner à pChaine l'adresse de Chaine.
Autre illustration de l'équivalence entre tableau de caractères et pointeur pChar :
Var
Chaine : Array
[0
..10
] of
Char
;
pChaine : pChar;
Caractere : Char
;
Begin
pChaine := Chaine;
(* Les 2 instructions suivantes font la même chose : *)
Caractere := Chaine[6
];
Caractere := pChaine[6
];
End
.
Toujours selon le même principe, voici un exemple de passage d'une chaîne AZT comme paramètre à une procédure :
Var
Chaine : Array
[0
..10
] of
Char
;
pChaine : pChar;
Procedure
TRAITEMENT (p : pChar);
Begin
{ Traitement }
End
;
Begin
pChaine := Chaine;
(* Les 2 instructions suivantes font la même chose : *)
TRAITEMENT(Chaine);
TRAITEMENT(pChaine);
End
.
2ème notion à retenir : Les constantes tableaux de caractères sont elles-aussi équivalentes aux pointeurs pChar.
3ème notion à retenir : Les paramètres de type chaîne AZT sont passés aux procédures et fonctions par adresse.
Nous pouvons donc aussi passer une constante tableau de caractères comme paramètre à une procédure :
TRAITEMENT('hello'
);
Cette dernière instruction stocke la chaîne de caractères dans le segment de données du programme et passe son adresse à la procédure.
Il est important de bien comprendre ce principe.
Il se passe exactement la même chose lorsqu'on assigne une constante tableau de caractères à notre pointeur pChar :
pChaine := 'tagada tsoin tsoin'
;
La chaîne est stockée dans le segment de données et son adresse est affectée à pChaine. La chaîne n'est donc aucunement recopiée.
II-A-1. En résumé▲
- Tableau de caractères (Array of Char) et pointeur pChar sont équivalents;
- Constante tableau de caractères ('Bonjour') et pointeur pChar sont équivalents;
- Les paramètres de type chaîne AZT sont passés aux procédures et fonctions par adresse.
II-B. Opérations sur les chaînes à zéro terminal▲
L'unité Strings de Virtual Pascal propose pas mal de routines de gestion des chaînes AZT :
Unit
Strings;
Interface
Function
StrLen (Str : pChar) : Word
;
(* Retourne la longueur d'une chaîne (0 terminal non compris) *)
Function
StrCopy (Dest, Source : pChar) : pChar;
(* Copie Source dans Dest *)
Function
StrECopy (Dest, Source : pChar) : pChar;
(* Copie Source dans Dest et retourne un pointeur sur le 0 terminal *)
Function
StrLCopy (Dest, Source : pChar; MaxLen : Word
) : pChar;
(* Copie MaxLen caractères de Source vers Dest *)
Function
StrCat (Dest, Source : pChar) : pChar;
(* Ajoute Source à Dest *)
Function
StrLCat (Dest, Source : pChar; MaxLen : Word
) : pChar;
(* Ajoute Source à Dest dans la limite de MaxLen caractères *)
Function
StrMove (Dest, Source : pChar; Count: Word
) : pChar;
(* Transfère Count caractères de Source vers Dest *)
Function
StrComp (Str1, Str2 : pChar) : Integer
;
(* Compare deux chaînes *)
Function
StrIComp (Str1, Str2 : pChar) : Integer
;
(* Compare deux chaînes sans tenir compte des majuscules/minuscules *)
Function
StrLComp (Str1, Str2 : pChar; MaxLen : Word
) : Integer
;
(* Compare deux chaînes sur une longueur de MaxLen caractères *)
Function
StrLIComp (Str1, Str 2
: pChar; MaxLen : Word
) : Integer
;
(* Compare deux chaînes sur une longueur de MaxLen caractères
sans tenir compte des majuscules/minuscules *)
Function
StrPos (Str1, Str2 : pChar) : pChar;
(* Retourne l'adresse de la 1ère occurence de Str2 au sein de Str1 *)
Function
StrScan (Str : pChar; Chr : Char
) : pChar;
(* Retourne l'adresse de la 1ère occurence du caractère Chr dans Str *)
Function
StrRScan (Str : pChar; Chr : Char
) : pChar;
(* Retourne l'adresse de la dernière occurence du caractère Chr dans Str *)
Function
StrEnd (Str : pChar) : pChar;
(* Retourne un pointeur sur le 0 terminal d'une chaîne *)
Function
StrUpper (Str : pChar) : pChar;
(* Convertit une chaîne en majuscules *)
Function
StrLower (Str : pChar) : pChar;
(* Convertit une chaîne en minuscules *)
Function
StrPas (Str : pChar) : String
;
(* Convertit une chaîne AZT en String *)
Function
StrPCopy (Dest : pChar; Source : String
) : pChar;
(* Copie une String dans une chaîne AZT *)
Function
StrNew (Str : pChar) : pChar;
(* Alloue dynamiquement une copie de chaîne et renvoie son adresse *)
Procedure
StrDispose (Str : pChar);
(* Désalloue une chaîne allouée par StrNew *)
Passons en revue les opérations les plus courantes. Plusieurs des routines présentées ci-après possèdent des variantes, que nous aborderons en temps voulu tout au long du tutoriel.
II-B-1. Assigner une valeur à une chaîne▲
Pour assigner une valeur à une chaîne AZT, on utilise la fonction StrCopy :
StrCopy(Chaine,'Bonjour'
);
Physiquement, la chaîne 'Bonjour' est copiée dans la chaîne AZT. Il y a donc bien deux exemplaires de la chaîne à l'arrivée !
Il peut être tentant, par analogie avec les Strings, d'assigner une valeur à une chaîne AZT comme ceci :
Chaine := 'Bonjour'
; (* Erreur de syntaxe ! *)
Mais le compilateur rejettera cette syntaxe ! Il faut employer StrCopy.
II-B-2. Initialiser une chaîne vide▲
Pour initialiser une chaîne vide, c'est tout simple : il suffit qu'elle commence par le zéro terminal !
Chaine[0
] := #0
;
II-B-3. Tronquer une chaîne▲
De la même manière, on peut tronquer une chaîne en y insérant un zéro terminal :
Chaine[5
] := #0
;
II-B-4. Déterminer la longueur d'une chaîne▲
Pour déterminer la longueur d'une chaîne AZT, on utilise la fonction StrLen :
Longueur := StrLen(Chaine);
Cette fonction compte les caractères de la chaîne jusqu'au zéro terminal.
II-B-5. Copier une chaîne▲
StrCopy sert également à copier une chaîne 1 dans une chaîne 2 :
StrCopy(Chaine2,Chaine1);
Retenez bien l'ordre des paramètres : destination avant source !
Après exécution de cette instruction, il y a bien deux exemplaires de la chaîne de départ.
Par contre (pour vous torturer un peu les méninges), combien y a-t-il d'exemplaires de la chaîne 1 après exécution de l'instruction suivante ?
Chaine2 := Chaine1;
Réponse : un seul exemplaire, bien sûr ! Cette instruction ne fait que copier l'adresse de la chaîne 1 dans la chaîne 2 et donc les deux pointeurs pointent sur la même chaîne en mémoire.
II-B-6. Comparer deux chaînes▲
Pour comparer deux chaînes AZT, vous avez le choix entre StrComp (qui tient compte des majuscules et minuscules) et StrIComp (qui n'en tient pas compte).
La valeur retournée par ces deux fonctions peut être positive, négative ou nulle :
Var
Chaine1 : Array
[0
..40
] of
Char
;
Chaine2 : Array
[0
..30
] of
Char
;
Resultat : Integer
;
Resultat := StrIComp(Chaine1,Chaine2);
if
Resultat > 0
then
WriteLn('Chaine1 > Chaine2'
)
else
if
Resultat < 0
then
WriteLn('Chaine1 < Chaine2'
)
else
WriteLn('Chaine1 = Chaine2'
);
II-B-7. Déterminer la position d'une chaîne dans une autre▲
Pour déterminer la position d'une chaîne dans une autre, on utilise StrPos.
Alors que la fonction Pos (pour les chaînes à attribut de longueur du Pascal) renvoie un indice, la fonction StrPos retourne un pointeur.
Un petit exemple :
Program
POSITION;
Uses
WinCRT,Strings;
Var
Chaine : Array
[0
..40
] of
Char
;
Extrait : Array
[0
..15
] of
Char
;
Adresse : pChar;
Begin
StrCopy(Chaine,'Salut la compagnie !'
);
StrCopy(Extrait,'compagnie'
);
Adresse := StrPos(Chaine,Extrait);
WriteLn(Adresse);
WriteLn('Indice de l''extrait : '
,Adresse - Chaine);
End
.
Ne tenez pas compte pour l'instant de la déclaration de l'unité WinCRT : elle fera l'objet du chapitre suivant.
Compilez et exécutez ce petit programme; vous obtiendrez quelque chose comme ceci :
Tout cela mérite quelques explications.
Tout d'abord, la variable Adresse retournée par l'instruction
Adresse := StrPos(Chaine,Extrait);
porte bien son nom puisqu'elle contient l'adresse de la chaîne 'compagnie' dans la chaîne 'Salut la compagnie !'. Pour bien le prouver, l'instruction
WriteLn(Adresse);
affiche à l'écran : compagnie !, c'est-à -dire à partir de l'adresse Adresse jusqu'au zéro terminal.
Enfin, notre dernière instruction soustrait l'adresse de la chaîne de départ à l'adresse trouvée par StrPos pour donner l'indice de l'extrait.
Non, il n'y a pas d'erreur : l'indice du 1er caractère est 0 et non 1 comme pour les chaînes à attribut de longueur : l'indice affiché est bien 9 et non 10.
II-B-8. Concaténer deux chaînes▲
Pour concaténer deux chaînes AZT, il existe l'instruction StrCat :
Var
Chaine1 : Array
[0
..40
] of
Char
;
Chaine2 : Array
[0
..30
] of
Char
;
StrCat(Chaine1,Chaine2);
L'instruction ci-dessus ajoute le contenu de Chaine2 Ã Chaine1.
Que se passe-t-il si le contenu des deux chaînes mis bout à bout dépasse la taille limite de Chaine1 ? Un beau plantage à courte échéance !
Retenez bien ceci : la plupart des routines de l'unité Strings n'effectuent aucune vérification de débordement !
Il incombe donc au programmeur de prendre ses précautions. Dans le cas présent, une de ces précautions peut être de choisir la variante StrLCat :
Var
Chaine1 : Array
[0
..40
] of
Char
;
Chaine2 : Array
[0
..30
] of
Char
;
StrLCat(Chaine1,Chaine2,SizeOf(Chaine1) - 1
);
Traduite en langage courant, cette instruction signifie : "ajouter à Chaine1 le contenu de Chaine2 en veillant à ne pas dépasser la taille maximale de Chaine1".
II-B-9. Conversion entre chaînes AZT et Strings▲
Il est possible de convertir une chaîne AZT en String à l'aide de la fonction StrPas :
Program
CONVERS1;
Uses
Strings;
Var
s : String
[7
];
c : Array
[0
..7
] of
Char
;
Begin
StrCopy(c,'Bonjour'
);
s := StrPas(c);
End
.
La conversion inverse se fait avec la fonction StrPCopy :
Program
CONVERS2;
Uses
Strings;
Var
s : String
[7
];
c : Array
[0
..7
] of
Char
;
Begin
s := 'Bonjour'
;
StrPCopy(c,s);
End
.
II-B-10. Allocation dynamique de chaînes AZT▲
L'allocation dynamique de chaînes AZT permet de tirer parti de la quantité de mémoire disponible dans le tas global de Windows, bien supérieure à la capacité d'un segment de données ou d'un segment de pile.
La fonction StrNew permet d'allouer une chaîne AZT :
Var
p : pChar;
p := StrNew('Allocation dans le tas'
);
Cette instruction alloue l'espace nécessaire dans le tas, y copie la chaîne et retourne l'adresse du bloc alloué.
Il y a donc deux copies de la chaîne : une dans le segment de données et l'autre dans le tas.
Le principe de l'allocation dans le tas est économe en mémoire car n'est alloué que l'espace nécessaire pour stocker une copie de chaîne avec le zéro terminal.
Illustration :
Var
Chaine : Array
[0
..255
] of
Char
;
p : pChar;
Begin
StrCopy(Chaine,'Allocation dans le segment de donn'#233'es'
);
p := StrNew(Chaine);
WriteLn(p);
StrDispose(p);
End
.
StrNew alloue dans le tas l'espace nécessaire pour stocker non pas 255 caractères (+ le zéro terminal) mais seulement 36 caractères (+ le zéro terminal) !
Notez que si la longueur de la chaîne est nulle ou s'il n'y a plus assez de mémoire dans le tas pour allouer la chaîne, StrNew renverra Nil.
Remarquez que p peut parfaitement être utilisé avec l'instruction WriteLn pour afficher la chaîne allouée dans le tas.
La fonction qui permet de libérer dans le tas l'espace alloué à une chaîne AZT est StrDispose.