Langage d'assemblage - Traitement des chaînes de caractères
Le traitement des chaînes de caractères est un besoin courant en programmation.
La gestion des chaînes en assembleur consiste à manipuler directement des séquences d'octets en mémoire ; c'est plus bas niveau mais aussi plus flexible.
Définition et stockage des chaînes de caractères
En NASM, les chaînes sont essentiellement des séquences d'octets et peuvent être définies à l'aide de la pseudo-instruction DB :
Exemple
section .data
; Méthode 1 : chaîne standard
str1 db 'Hello, example!', 0 ; style C : terminée par un caractère nul
; Méthode 2 : avec un saut de ligne
str2 db 'Line1', 0xA, 'Line2', 0xA
; Méthode 3 : les backticks prennent en charge les séquences d'échappement
str3 db `hello\nworld\n`, 0 ; Convertit automatiquement les séquences d'échappement
; Méthode 4 : définir caractère par caractère
str4 db 'A', 'B', 'C', 'D', 0
; Méthode 5 : utiliser dup pour générer des caractères répétés
border db 40 dup('-') ; 40 tirets
; Calcul de la longueur de chaîne (à la compilation)
str1_len equ $ - str1 ; Y compris le null final
Calcul de la longueur de chaîne
Il y a deux façons d'obtenir la longueur d'une chaîne : le calcul à la compilation (EQU) et le calcul à l'exécution (parcourir pour trouver le null) :
Exemple
; Deux façons de calculer la longueur d'une chaîne
section .data
; Méthode A : calcul à la compilation (pour les chaînes constantes)
msg1 db 'Hello, EXAMPLE!', 0xA
msg1_len equ $ - msg1 ; Calculé automatiquement au moment de la compilation
; Méthode B : chaîne terminée par un caractère nul (calcul à l'exécution)
msg2 db 'Find my length', 0 ; Se termine par 0
section .text
global _start
_start:
; Méthode A : utiliser directement la longueur calculée au moment de la compilation
mov eax, 4
mov ebx, 1
mov ecx, msg1
mov edx, msg1_len ; Utiliser directement la constante
int 0x80
; Méthode B : calculer la longueur d'une chaîne terminée par un caractère nul à l'exécution
mov esi, msg2 ; esi pointe vers le début de la chaîne
mov ecx, 0 ; compteur
strlen_loop:
cmp byte [esi], 0 ; L'octet actuel est-il nul ?
je strlen_done ; Oui, fin
inc ecx ; compteur + 1
inc esi ; pointeur + 1
jmp strlen_loop
strlen_done:
; ecx contient maintenant la longueur de la chaîne (sans le caractère nul)
mov eax, 4
mov ebx, 1
mov ecx, msg2
mov edx, ecx ; Utiliser la longueur calculée
; Voici un problème : ecx est écrasé, il faudrait d'abord le sauvegarder
; Approche correcte : push ecx puis pop dans edx
mov eax, 1
mov ebx, 0
int 0x80
Copie de chaîne
Utiliser une boucle pour copier octet par octet, ou utiliser les instructions de traitement de chaînes x86MOVSB:
Exemple
; Deux implémentations de copie de chaînes
section .data
src db 'example source string', 0
src_len equ $ - src
section .bss
dest_manual resb 64 ; Cible de copie manuelle
dest_fast resb 64 ; Cible de copie rapide
section .text
global _start
_start:
; Méthode A : copie manuelle octet par octet
mov esi, src ; adresse source
mov edi, dest_manual ; adresse de destination
mov ecx, src_len ; nombre d'octets
copy_loop:
mov al, [esi] ; lire un octet
mov [edi], al ; écrire un octet
inc esi ; pointeur source++
inc edi ; pointeur de destination++
loop copy_loop
; Méthode B : utiliser les instructions de chaîne (plus rapide)
cld ; Effacer le drapeau de direction (DF=0, copie vers l'avant)
mov esi, src ; adresse source (ESI)
mov edi, dest_fast ; adresse de destination (EDI)
mov ecx, src_len ; nombre d'octets (ECX)
rep movsb ; exécuter répétitivement movsb ecx fois
; rep movsb:while(ecx>0) { [edi]=[esi]; esi++; edi++; ecx--; }
; Vérifier : afficher les deux résultats de copie
mov eax, 4
mov ebx, 1
mov ecx, dest_manual
mov edx, src_len
int 0x80
mov eax, 4
mov ebx, 1
mov ecx, dest_fast
mov edx, src_len
int 0x80
mov eax, 1
mov ebx, 0
int 0x80
REP MOVSBC'est la méthode de copie de chaînes la plus classique sur x86. Cependant, sur les CPU modernes, une boucle de copie manuelle avec déroulage de boucle peut être plus rapide que REP MOVSB, car les CPU modernes ont une meilleure optimisation du pipeline pour les opérations simples.
Comparaison de chaînes
UtiliserCMPSBavecREPE(répéter jusqu'à ce qu'ils diffèrent) comparaison octet par octet :
Exemple
; Comparer si deux chaînes sont égales
section .data
str_a db 'exemple', 0
str_b db 'exemple', 0
str_c db 'EXEMPLE', 0
eq_msg db 'Les chaînes sont égales', 0xA
eq_len equ $ - eq_msg
ne_msg db 'Les chaînes ne sont PAS égales', 0xA
ne_len equ $ - ne_msg
section .text
global _start
_start:
cld ; Comparaison avant
mov esi, str_a ; Première chaîne
mov edi, str_b ; Deuxième chaîne
mov ecx, 7 ; Comparer jusqu'à 7 octets (y compris le caractère nul)
repe cmpsb ; Répéter la comparaison jusqu'à ce qu'elles diffèrent ou ecx=0
; repe: continuer si ZF=1 (égal) et ecx>0
; cmpsb: comparer [esi] et [edi], puis esi++, edi++
je strings_equal ; Si ZF=1, tous les octets sont égaux
; Pas égales
mov eax, 4
mov ebx, 1
mov ecx, ne_msg
mov edx, ne_len
int 0x80
jmp compare_next
strings_equal:
mov eax, 4
mov ebx, 1
mov ecx, eq_msg
mov edx, eq_len
int 0x80
compare_next:
; Comparer str_a et str_c (casse différente)
cld
mov esi, str_a
mov edi, str_c
mov ecx, 7
repe cmpsb
jne not_equal_2
mov eax, 4
mov ebx, 1
mov ecx, eq_msg
mov edx, eq_len
int 0x80
jmp exit
not_equal_2:
mov eax, 4
mov ebx, 1
mov ecx, ne_msg
mov edx, ne_len
int 0x80
exit:
mov eax, 1
mov ebx, 0
int 0x80
Résumé des instructions d'opération sur les chaînes
| Instruction | Fonction | Registres utilisés |
|---|---|---|
| MOVSB | Copier l'octet : [EDI] = [ESI] | ESI=source, EDI=destination, ECX=compteur, DF=direction |
| MOVSW | Copier un mot (2 octets) | Comme ci-dessus |
| MOVSD | Copier un double mot (4 octets) | Comme ci-dessus |
| STOSB | Stocker un octet : [EDI] = AL | EDI=destination, AL=valeur, ECX=compteur |
| LODSB | Charger un octet : AL = [ESI] | ESI=source |
| CMPSB | Comparer un octet : [ESI] - [EDI] | ESI=source, EDI=destination, ECX=compteur |
| SCASB | Balayer un octet : AL - [EDI] | EDI=destination, AL=valeur recherchée, ECX=compteur |
Exemple de conversion de casse
Exemple
; Convertir les lettres minuscules d'une chaîne en majuscules
section .data
msg db 'Hello, example! Welcome to Assembly.', 0xA
len equ $ - msg
section .text
global _start
_start:
; Afficher la chaîne d'origine
mov eax, 4
mov ebx, 1
mov ecx, msg
mov edx, len
int 0x80
; Conversion : parcourir la chaîne, minuscules -> majuscules
mov esi, msg ; Pointer vers le début de la chaîne
mov ecx, len ; Nombre d'itérations de la boucle
convert_loop:
mov al, [esi] ; Lire le caractère
cmp al, 'a' ; Est-il >= 'a'
jb next_char ; Inférieur à 'a', sauter
cmp al, 'z' ; Est-il <= 'z'
ja next_char ; Supérieur à 'z', sauter
; Minuscule vers majuscule : 'a'(97) - 'A'(65) = 32
sub al, 32 ; Convertir en majuscules
mov [esi], al ; Réécrire
next_char:
inc esi
loop convert_loop
; Afficher la chaîne convertie
mov eax, 4
mov ebx, 1
mov ecx, msg
mov edx, len
int 0x80
mov eax, 1
mov ebx, 0
int 0x80
Résultat de l'exécution:
$ nasm -f elf32 case_convert.asm -o case_convert.o $ ld -m elf_i386 case_convert.o -o case_convert $ ./case_convert Hello, example! Welcome to Assembly. HELLO, EXAMPLE! WELCOME TO ASSEMBLY.
Autres extensionsLe drapeau de direction DF détermine le sens des opérations sur chaînes : lorsque DF=0, ESI/EDI s'incrémentent (vers l'avant), lorsque DF=1, ils se décrémentent (vers l'arrière). Utilisez
CLDpour effacer DF, utilisezSTDpour définir DF. Avant d'appeler des fonctions C ou des appels système, assurez-vous que DF=0 (exigence cdecl).