Utilisation des fonctions de chaine
Page 1 sur 1
Utilisation des fonctions de chaine
Un programme simple pour montrer l'utilisation de chaines de caractères en assembleur avec les fonctions de l'API Windows.
Ce programme affiche dans la console une invite de saisie de mots (fonctions GetStdHandle, lstrlen et writeFile) Remarque : la fonction lstrlen qui calcule la longueur d' une chaine ne fonctionne pas si la chaine est vide !!!)
Il récupère la chaine saisie par l'utilisateur (fonctions GetStdHandle,ReadConsoleA). Ensuite il découpe la chaine en mots (le séparateur étant quelconque car il utilise la fonction IsCharAlphaNumericA) et hélas, je n'ai pas trouvé de fonctions de l'API qui le fasse.
Pour le découpage, nous enregistrons dans une table l'adresse du début de chaque mot contenu dans le buffer et nous écrasons le séparateur en mettant un zéro ainsi chaque mot forme une chaine correcte. La procèdure retourne le nombre de mots trouvés.
Ensuite, le programme va afficher chaque mot trouvé puis le stocker dans une structure d'arbre binaire, histoire de manipuler des pointeurs.
Chaque mot est recherché dans l'arbre en le comparant à ceux déjà stockés avec la fonction lstrcmp, s'il est trouvé un compteur est incrémenté pour comptage sinon il est inséré dans le tas sous forme d'une structure qui contient le pointeur gauche, le pointeur droit, le compteur puis le mot complet. Celui ci est copié du buffer dans la structure en utilisant la fonction lstrcpy. Si la casse ne doit pas jouer, il est possible d'utiliser la fonction de comparaison lstrcmpi. Après stockage, il est nécessaire de mettre à jour le pointeur du tas pour stocker le mot suivant.
Quand la saisie est terminée (test de la chaine #fin mais il est possible aussi de rester une chaine vide), l'arbre crée est balayé de manière récursive pour afficher tous les mots et le compteur par ordre alphabétique.
Pour l'affichage, j'aurais pu me contenter d'utiliser la fonction wsprintfa pour formater le message complet mais j'ai utilisé les fonctions lstrcpy et lstrcat pour montrer la concaténation de chaines.
Pour des explications sur l'utilisation des arbres binaires, voir un bon manuel d’algorithmes ( Livres de Knuth par exemple).
Voici donc quelques fonctions possibles mais hélas, je n'ai pas trouvé de fonction pour chercher une sous chaine dans une chaine, ni rempacer une sous-chaine par une autre etc. !!!
Ce programme affiche dans la console une invite de saisie de mots (fonctions GetStdHandle, lstrlen et writeFile) Remarque : la fonction lstrlen qui calcule la longueur d' une chaine ne fonctionne pas si la chaine est vide !!!)
Il récupère la chaine saisie par l'utilisateur (fonctions GetStdHandle,ReadConsoleA). Ensuite il découpe la chaine en mots (le séparateur étant quelconque car il utilise la fonction IsCharAlphaNumericA) et hélas, je n'ai pas trouvé de fonctions de l'API qui le fasse.
Pour le découpage, nous enregistrons dans une table l'adresse du début de chaque mot contenu dans le buffer et nous écrasons le séparateur en mettant un zéro ainsi chaque mot forme une chaine correcte. La procèdure retourne le nombre de mots trouvés.
Ensuite, le programme va afficher chaque mot trouvé puis le stocker dans une structure d'arbre binaire, histoire de manipuler des pointeurs.
Chaque mot est recherché dans l'arbre en le comparant à ceux déjà stockés avec la fonction lstrcmp, s'il est trouvé un compteur est incrémenté pour comptage sinon il est inséré dans le tas sous forme d'une structure qui contient le pointeur gauche, le pointeur droit, le compteur puis le mot complet. Celui ci est copié du buffer dans la structure en utilisant la fonction lstrcpy. Si la casse ne doit pas jouer, il est possible d'utiliser la fonction de comparaison lstrcmpi. Après stockage, il est nécessaire de mettre à jour le pointeur du tas pour stocker le mot suivant.
Quand la saisie est terminée (test de la chaine #fin mais il est possible aussi de rester une chaine vide), l'arbre crée est balayé de manière récursive pour afficher tous les mots et le compteur par ordre alphabétique.
Pour l'affichage, j'aurais pu me contenter d'utiliser la fonction wsprintfa pour formater le message complet mais j'ai utilisé les fonctions lstrcpy et lstrcat pour montrer la concaténation de chaines.
Pour des explications sur l'utilisation des arbres binaires, voir un bon manuel d’algorithmes ( Livres de Knuth par exemple).
Voici donc quelques fonctions possibles mais hélas, je n'ai pas trouvé de fonction pour chercher une sous chaine dans une chaine, ni rempacer une sous-chaine par une autre etc. !!!
- Code:
;---programme 64 bits
; fonctions de chaines de caractères
; affichage dans la console
%include "../windowsinc64.inc"
;=======================================
; segment des données initialisées
;=======================================
segment .data
;code page 850 é=82h à=85h è=8Ah ê=88h
szChForm db "%u ",0 ; format décimal pour la fonction wsprintfA
szMessInvite db "Veuillez saisir une liste de mots (saisir #fin pour terminer):",10,13,0 ; les chaines
szMessFin db 10,13,"Fin du programme.",0 ;doivent se terminer par 0
szRetourLigne db 10,13,0 ; les caractères 10, 13 entainent un saut de ligne
szChaineFin db "#fin",0 ; pour comparaison et arrêter la saisie
szTablePetite db "Table trop petite !!",10,13,0
szTasPetit db "Le tas est trop petit !!",10,13,0
szChInter db " compteur = ",0 ; chaine intermédiaire
;
;structure d'un arbre
struc ARBRE
.gauche resq 1 ; pointeur branche gauche
.droit resq 1 ; pointeur branche droite
.cpt resq 1 ; données compteur
.debutcle resb 0 ; la chaine sera stockée à partir de cette adresse (et c'est bien zéro)
endstruc
iarbre istruc ARBRE ; instance de l'arbre mais ne sert en fait que pour calculer la longueur
iend ; de la structure
ILGARBRE equ $ - iarbre
ptArbre dq sArbre ; pointeur donnant la première position libre de l'arbre
;=======================================
; segment des données non initialisées
;=======================================
segment .bss
hMain resq 1
INBMAXI equ 100
ptTableMots resq INBMAXI
szResult resb 100
ILGBUFFER equ 200
szBuffer resb ILGBUFFER
ILGTASARBRE equ 50000
sArbre resb ILGTASARBRE ; tas contenant l'arbre
;=======================================
; segment de code
;=======================================
segment .text
global Main
extern afferreur
Main:
sub rsp, 8h ; alignement de la pile avant tout appel de procédure
sub rsp,20h
;recup handle de l'instance du programme
mov rcx,NULL
call GetModuleHandleA
mov r9,__LINE__ - 1
cmp eax,NULL
je .gestionerreurs
;init debut arbre
mov rcx,sArbre ; adresse du début de l'arbre
mov rdx,ptArbre ; adresse du pointeur de la première position libre de l'arbre
call initarbre
.boucle:
;ecrire le message d'invite dans la console
mov rcx,szMessInvite
call affconsolemess
;lire la chaine de caractères saisie
mov rcx,szBuffer ; adresse du buffer pour récuperer la saisie
mov rdx,ILGBUFFER ; longueur de ce buffer
call saisieClavier
;push __LINE__
;call afftoutregistreP
;affichage de la chaine saisie
mov rcx,szBuffer
call affconsolemess
mov rcx,szRetourLigne ; mais il faut aussi afficher un retour ligne
call affconsolemess
;si egale à #fin terminer le programme
mov rcx,szBuffer
mov rdx,szChaineFin
call lstrcmp
cmp eax,0
je .fin1
;push __LINE__
;call afftoutregistreP
;eclater en mots distincts
mov rcx,szBuffer ; adresse du buffer (attention il sera modifié)
mov rdx,ptTableMots ; adresse de la table des pointeurs de chaque mot
mov r8,INBMAXI ; taille de la table
call eclatement
;push __LINE__
;call afftoutregistreP
;controle eclatement
mov r12,rax
mov rbx,0
.boucleV:
cmp rbx,r12
jge .finboucleV
mov rcx,[ptTableMots + rbx*8] ; recuperation dans la table du pointeur vers le début d'un mot
call affconsolemess
mov rcx,szRetourLigne ; mais il faut aussi afficher un retour ligne
call affconsolemess
;stocker les mots dans un arbre pour comptage
mov rcx,[ptTableMots + rbx*8] ; adresse du mot à stocker
mov rdx,sArbre ; adresse début de l'arbre
mov r8,ptArbre ; adresse du pointeur de la position libre du tas
call stockage
inc rbx
jmp .boucleV
.finboucleV:
jmp .boucle ; boucle principale
.fin1: ; faut afficher les résultats dans la console
mov rcx,sArbre ; debut de l'arbre
call balayagealpha ; par ordre alphabetique
mov rax,0
jmp .main_fin
.gestionerreurs:
call afferreur
mov rax,1
jmp .fin
.main_fin:
mov rcx,szMessFin ; message de fin
call affconsolemess
mov rcx,szBuffer ; Pause
mov rdx,ILGBUFFER
call saisieClavier
.fin:
mov rcx,rax ; code retour
call ExitProcess ; fin du programme
;========================================
;affichage message dans console
;========================================
;rcx contient le message à afficher
affconsolemess:
%define .pMess [rbp-8]
%define .hCons [rbp-16]
enter 16,0
cmp byte [rcx],0 ; si le message est vide on ne fait rien
je .fin_aff
mov .pMess,rcx ; save message
sub rsp, 20h ; ajuste RSP pour la sauvegarde des paramètres 20h
mov rcx,STD_OUTPUT_HANDLE ; code pour la sortie standard
call GetStdHandle ; acquisition du handle de la sortie standard
mov r9,__LINE__ - 1
cmp eax,NULL
je .erreur
mov .hCons, rax ; stockage du handle de la console
;calcul de la longueur du message
mov rcx,.pMess; recup de l'adresse du message à afficher
call lstrlen ; cette fonction ne fonctionne pas si le message est vide
mov r9,__LINE__ - 1
cmp eax,NULL
je .erreur
add rsp,20h ; on rend la place des parametres précédent
sub rsp, 8h ; donc on enleve déjà 8 octets pour preparer la pile
push 0 ; avant le push du parametre qui enleve 8 octets sur la pile
sub rsp, 20h ; puis on enleve les 32 octets pour la place des 4 parametres
mov rcx,.hCons ;handle console
mov rdx,.pMess
mov r8,rax ; longueur calculee avant
mov r9,NULL
call WriteFile
add rsp, 10h ; dépile le paramètre pushe et son alignement
mov r9,__LINE__ - 2
cmp eax,NULL
je .erreur
mov rax,TRUE
jmp .fin_aff
.erreur:
call afferreur
mov rax,FALSE
.fin_aff:
leave ; pendant du enter, remet la pile en l'état
ret
;=========================================================
;lecture clavier
;=========================================================
;rcx contient l'adresse du buffer
;rdx contient la longueur maxi du buffer
;
saisieClavier:
%define .pBuff [rbp-8]
%define .iLong [rbp-16]
%define .iByteLus [rbp-24]
enter 32,0 ; reserve 4 fois 8 octets
mov .pBuff,rcx
mov .iLong,rdx
sub rsp,20h
mov rcx,STD_INPUT_HANDLE
call GetStdHandle ; recup de handle ( STD_INPUT_HANDLE)
mov r9,__LINE__
cmp eax,NULL
je .erreur
add rsp,20h
sub rsp,8h ; car un seul push
push 0 ; parametre de fin de saisie (ici standard)
sub rsp,20h
mov rcx,rax ; handle fonction précedente
mov rdx,.pBuff ; buffer
mov r8,.iLong
lea r9, .iByteLus ; nb octets lus
call ReadConsoleA
add rsp,10h ; le push et l'alignement
mov r9,__LINE__
cmp eax,NULL
je .erreur
mov rcx,.pBuff
mov rax,.iByteLus
sub rax,2
mov byte [rcx+rax],0 ; forçage du 00 à la place du 0D final
; rax contient la longueur de la chaine hors 0 final
jmp .fin
.erreur:
call afferreur
mov rax,-1 ; si erreur retour - 1
.fin:
leave
ret
;==============================================
;Eclatement d'une chaine en mot
;le separateur est tout caractère non alpha numerique
; ATTENTION, le buffer d'entrée est modifié !!
;==============================================
; rcx contient l'adresse du buffer à eclater
; rdx contient le pointeur de début de table
;utilise rsi,rbx,r12,r13,r14
;retourne le nombre de mots trouves dans rax ou -1 si erreur
eclatement:
sub rsp,28h
mov r13,rdx ; pointeur debut de table
mov rbx,0 ; nombre de mots
mov rsi,rcx ; pointeur d'un caractere du buffer
mov r12,0 ; compteur de caracteres
xor r14,r14 ; top pour signaler un mot
xor rcx,rcx ; raz du registre
.boucle:
cmp byte[rsi+r12],0 ; c'est la fin du buffer on termine
je .fin1
mov cl,[rsi+r12] ; sinon on recupere le caractères
call IsCharAlphaNumericA ; est-il valide ?
cmp rax,0
je .suiteb ; non
cmp r14,1 ; as-ton commencé un mot
je .suiteboucle ; oui donc on continue
add rsi,r12 ; on ajoute le compteur precedent
xor r12,r12 ; et on le remet à zero
mov [r13+rbx*8],rsi ; stockage du debut du mot dans la table
mov r14,1 ; top pour indiquer qu'un mot est en cours
jmp .suiteboucle ; et on boucle
.suiteb: ;le carctere n'est pas alphanumerique
cmp r14,1 ;un mot est-il en cours ?
jne .suiteboucle
mov byte[rsi+r12],0 ; on écrase le caractère par 0
mov r14,0 ; on met le top à zero
inc rbx ; on incremente le compteur de mots
cmp rbx,r8
jge .erreur
jmp .suiteboucle
.suiteboucle:
inc r12
jmp .boucle
.fin1:
cmp r14,0 ; as-ton commencé un mot
je .fin2 ; non
mov [r13+rbx*8],rsi ; stockage du debut du mot dans la table
inc rbx
.fin2:
mov rax,rbx
jmp .fin
.erreur:
mov rcx,szTablePetite
call affconsolemess
mov rax,-1
.fin:
add rsp,28h
ret
;===============================================
;initialisation d'un arbre
;==============================================
;rcx contient l'adresse du debut de l'arbre
;rdx contient l'adresse du pointeur du tas
initarbre:
mov rax,rcx
mov [rax+ARBRE.gauche],rcx ; pointeurs initialisés avec le début de l'arbre
mov [rax+ARBRE.droit],rcx
mov qword [rax+ARBRE.cpt],0 ; raz du compteur
add qword[rdx],ILGARBRE ; on déplace le pointeur de fin de la longueur structure arbre
ret
;===================================================================
; stockage des mots trouvés dans un arbre
;===================================================================
;rcx pointeur du mot a chercher
;rdx contient l'adresse du début de l'arbre
;r8 contient l'adresse du pointeur de la première place libre du tas
;utilise r8,rsi,r13,r14,r15
stockage:
%define .pDebutArbre [rbp-8]
%define .Sens [rbp-16]
%define .pTasArbre [rbp-24]
enter 32,0
sub rsp,20h
mov r14,rdx ; adresse du père initialisée avec le debut arbre
mov .pDebutArbre,rdx ; adresse debut arbre sert de pointeur d'arrêt
mov .pTasArbre,r8
mov r15,rcx ; adresse du mot à inserer
mov rsi,[r14+ARBRE.droit] ; pointeur de balayage de l'arbre
mov qword .Sens,0 ; on part à droite
.boucle:
cmp rsi,.pDebutArbre ; le pointeur est arrivé sur un noeud vide
je .insert ; on insere le mot
mov r14,rsi ; sinon on va balayer et on garde le pointeur comme nouveau père
;comparaison des clés
mov rcx,r15 ; mot en entree
lea rdx,[rsi+ARBRE.debutcle] ; mot déjà stocké
call lstrcmp
cmp eax,0 ; attention il faut seulement comparer eax
je .egalite
jl .gauche
mov rsi,[rsi+ARBRE.droit] ; plus grand on suit le pointeur de droite
mov qword .Sens,0 ; on part à droite
jmp .boucle
.gauche: ; plus petit on suit le pointeur de gauche
mov rsi,[rsi+ARBRE.gauche]
mov qword .Sens,1 ; on part à gauche
jmp .boucle
;
;le balayage est fini
.egalite: ; trouvé on le compte
inc qword[rsi+ARBRE.cpt]
jmp .fin
.insert: ; on n'a pas trouvé le mot donc on le stocke en fin du tas
mov rax,.pTasArbre ; récuperation de l'adresse du pointeur de place vide
mov r13,[rax] ; pointeur de place vide
mov rax,.pDebutArbre
mov [r13+ARBRE.droit],rax ; on initialise les pointeurs avec l'adresse de debut
mov [r13+ARBRE.gauche],rax
mov qword[r13+ARBRE.cpt],1 ; on met le compteur à 1
lea rcx,[r13+ARBRE.debutcle] ; et on copie le mot ensuite
mov rdx,r15
call lstrcpy
mov rcx,r15 ; hélas cette fonction ne rend pas la longueur copiée (A VERIFIER )
call lstrlen ; calcul longueur du mot stocké
inc rax ; pour le caractère de fin de chaine
add rax,ILGARBRE ; pour les zones de l'arbre
mov r8,.pTasArbre ; utilisation temporaire du registre r8
add [r8],rax ; on augmente le pointeur de tas de la longueur stockée
mov rax,[r8] ; verification si on dépasse pas la taille allouée
sub rax,.pDebutArbre
cmp rax,ILGTASARBRE
jge .erreurTas
;
;mise à jour du pointeur du père avec le début du nouveau noeud crée
cmp qword .Sens,0
jne .gauche1
mov [r14+ARBRE.droit],r13
jmp .fin
.gauche1:
mov [r14+ARBRE.gauche],r13
mov rax,0
jmp .fin
.erreurTas:
mov rcx,szTasPetit
call affconsolemess
mov rax,-1
.fin:
leave
ret
;================================================================
;balayage arbre par ordre alphabetique
;================================================================
balayagealpha:
;rcx contient le début de l'arbre
sub rsp,8h
mov rdx,[rcx+ARBRE.droit] ; on initialise avec le pointeur droit du début de l'arbre
call balayagearbre ; et on laisse dans rcx l'adresse du début de l'arbre
add rsp,8h
ret
;================================================================
;balayage arbre par ordre alphabetique
;================================================================
;rcx contient le début de l'arbre
;rdx le pointeur (initialisé avec le pointeur droit)
balayagearbre:
%define .pCourant [rbp-8] ; necessaire car procèdure récursive
%define .pDebut [rbp-16] ; donc on garde les variables locales
enter 16,0
cmp rdx,rcx ; le pointeur est arrivée sur un noeud final
je .fin ; donc on retourne
mov .pDebut,rcx ; sinon on balaye la partie gauche récursivement
mov .pCourant,rdx
mov rdx,[rdx+ARBRE.gauche]
call balayagearbre
mov rcx,.pCourant ;puis on affiche le contenu du noeud
call affichage
mov rcx,.pDebut ; puis on balaye la partie droite recursivement
mov rax,.pCourant
mov rdx,[rax+ARBRE.droit]
call balayagearbre
.fin:
leave
ret
;=====================================
; affichage du contenu de chaque noeud
;=====================================
affichage:
sub rsp,28h
;conversion du compteur avec wsprinfa
mov rbx,rcx ; on utilise rbx comme sauvegarde de l'adresse
mov rcx,szResult ; buffer du resultat de la conversion
mov rdx,szChForm ; chaine de formatage
mov r8,[rbx+ARBRE.cpt] ; contenu du compteur
call wsprintfA
mov r9,__LINE__ - 1
cmp eax,NULL
je .erreur
;concatene chaine
mov rcx,szBuffer ; zone de reception
lea rdx,[rbx+ARBRE.debutcle] ; mot à afficher
call lstrcpy ; copie
mov r9,__LINE__ - 1
cmp eax,NULL
je .erreur
mov rcx,szBuffer ; zone de reception
mov rdx,szChInter ; chaine intermédiaire
call lstrcat ; on concatene
mov r9,__LINE__ - 1
cmp eax,NULL
je .erreur
mov rcx,szBuffer ; zone de reception
mov rdx,szResult ; resultat de la conversion du compteur
call lstrcat ; on concatene
mov r9,__LINE__ - 1
cmp eax,NULL
je .erreur
;affichage de la chaine ainsi creée
mov rcx,szBuffer
call affconsolemess
mov rcx,szRetourLigne ; mais il faut aussi afficher un retour ligne
call affconsolemess
jmp .fin
.erreur:
call afferreur
.fin:
add rsp,28h
ret
Page 1 sur 1
Permission de ce forum:
Vous ne pouvez pas répondre aux sujets dans ce forum
|
|