SUB puis un unsigned comparer est un bon moyen de vérifier une entrée étant dans une certaine plage en utilisant une seule branche conditionnelle, au lieu de comparer-et-branches distinctes pour >= 'A'
et <= 'Z'
.
Les compilateurs utilisent cette astuce lorsque c'est possible. Voir aussi Agner Fog's Optimizing Assembly guide, et d'autres liens dans le wiki tag x86 pour plus d'informations sur l'écriture d'asm efficace.
Vous pouvez même l'utiliser pour détecter des caractères alphabétiques (majuscules ou minuscules) avec une branche: OU avec 0x20 rendra n'importe quelle minuscule majuscule, mais ne rendra aucun caractère alphabétique non alphabétique. Faites-le, puis utilisez l'astuce non signée pour comparer les valeurs en minuscules. (Ou commencez par ET avec ~0x20
pour effacer ce bit, en forçant les majuscules). J'ai utilisé cette astuce dans an answer on flipping the case of alphabetic characters while leaving other characters alone.
Et oui, comme vous l'avez remarqué, ASCII est conçu de sorte que la différence entre les lettres majuscules/minuscules pour chaque lettre ne fait que retourner un bit. Chaque caractère minuscule a 0x20 défini, tandis que les majuscules l'ont effacé. ET/OU/XOR sont généralement préférables pour ce faire (par rapport à ADD/SUB), parce que vous pouvez parfois profiter de ne pas se soucier de l'état initial, en forçant à un cas.
Votre code a des trucs bizarres: PUSH AL
n'assemblez même pas avec la plupart des assembleurs, puisque la taille minimale pour push/pop est 16 bits. Il n'y a pas non plus de raison de sauvegarder/restaurer AL, parce que vous vous débarrassez de toute EAX juste après avoir restauré AL après la boucle!
En outre, MOV écrase juste sa destination, il n'y a donc pas besoin de xor bl,bl
.
En outre, vous utilisez BL comme un registre à gratter, mais il est faible octet de EBX (que vous utilisez comme un pointeur!)
Voici comment je pourrais le faire, en utilisant seulement EAX, ECX et EDX donc je Vous n'avez pas à sauvegarder/restaurer les registres. (Votre fonction clobbers EBX, dont la plupart des conventions d'appel 32 et 64 bits nécessitent des fonctions pour enregistrer/restaurer). J'aurais besoin d'un registre supplémentaire si string
n'était pas alloué statiquement, me laissant utiliser son adresse comme constante immédiate.
toLower2 PROC ;65-90 is upper, 97-122 is lower (XOR 32?)
mov edx, OFFSET string ; don't need LEA for this, and mov is slightly more efficient
add edx, strSize ; This should really be an equ definition, not a load from memory.
; edx starts at one-past-the-end, and we loop back to the start
loop1:
dec edx
movzx eax, byte [edx] ; mov al, [edx] leaving high garbage in EAX is ok, too, but this avoids a partial-register stall when doing the mov+sub in one instruction with LEA
lea ecx, [eax - 'A'] ; cl = al-'A', and we don't care about the rest of the register
cmp cl, 25 ;if(c >= 'A' && c <= 'Z') c |= 0x20;
ja NoCap
or al, 0x20 ; tolower
mov [edx], al ; since we're branching anyway, make the store conditional
NoCap:
cmp edx, OFFSET string
ja loop1
mov eax, edx
toLower2 ENDP
The LOOP instruction is slow, and should be avoided. Juste oubliez qu'il existe même et utilisez n'importe quelle condition de boucle est commode. Le fait de ne stocker le caractère que lorsque le personnage change rend le code plus efficace, car il ne salit pas le cache lorsqu'il est utilisé sur une mémoire qui n'a pas changé depuis un moment s'il n'y a rien à faire.
Au lieu de ja NoCap
, vous pourriez faire branchlessly avec un cmov. Mais maintenant je dois ignorer ma suggestion de préférer ET/OU au lieu de ADD/SUB, parce que nous pouvons utiliser LEA pour ajouter 0x20 sans affecter les drapeaux, nous enregistrez un registre.
loop1:
dec edx
movzx eax, byte [edx] ; mov al, [edx] leaving high garbage in EAX is ok, too, but this avoids a partial-register stall when doing the mov+sub in one instruction with LEA
lea ecx, [eax - 'A'] ; cl = al-'A', and we don't care about the rest of the register
cmp cl, 25 ;if(c >= 'A' && c <= 'Z') c += 0x20;
lea ecx, [eax + 0x20] ; without affecting flags
cmovna eax, ecx ; take the +0x20 version if it was in the uppercase range to start with
; al = tolower(al)
mov [edx], al
cmp edx, OFFSET string
ja loop1
Ajoutez '0x20' au caractère. si '0x61 <= char <= 0x7a', alors stocker. Sinon, passez au caractère suivant –
Ce code n'est pas valide, il écrasera une autre mémoire après le premier caractère. Avez-vous vérifié dans le débogueur, que fait-il? Ayant "?????" commenter après 'xor bl, bl' ne semble pas prometteur. Vous pouvez également utiliser 'subbl, 'A'' au lieu de" sub bl, 65 "(ou changer de compilateur, si cela ne fonctionne pas avec les littéraux de caractères) ... donc après avoir ajouté 0x20, vous pouvez tester" if below " a'' skip write "et" if above ''z'' skip écrivent" else "réécrivent la nouvelle valeur dans la chaîne". Pas besoin d'écrire ces caractères ASCII dans la source sous forme de nombres. – Ped7g
@ Ped7g: Vérifier les deux côtés d'une gamme avec une branche (en utilisant sub/cmp/unsigned-jcc), est en fait une bonne technique, et fonctionne bien. –