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

; Méthodes de définition des chaînes

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

; Chemin du fichier : strlen_demo.asm
; 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

; Chemin du fichier : strcpy_demo.asm
; 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

; Chemin du fichier : strcmp_demo.asm
; 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

InstructionFonctionRegistres utilisés
MOVSBCopier l'octet : [EDI] = [ESI]ESI=source, EDI=destination, ECX=compteur, DF=direction
MOVSWCopier un mot (2 octets)Comme ci-dessus
MOVSDCopier un double mot (4 octets)Comme ci-dessus
STOSBStocker un octet : [EDI] = ALEDI=destination, AL=valeur, ECX=compteur
LODSBCharger un octet : AL = [ESI]ESI=source
CMPSBComparer un octet : [ESI] - [EDI]ESI=source, EDI=destination, ECX=compteur
SCASBBalayer un octet : AL - [EDI]EDI=destination, AL=valeur recherchée, ECX=compteur

Exemple de conversion de casse

Exemple

; Chemin du fichier : case_convert.asm
; 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.

Le 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). UtilisezCLDpour effacer DF, utilisezSTDpour définir DF. Avant d'appeler des fonctions C ou des appels système, assurez-vous que DF=0 (exigence cdecl).

Autres extensions