Quel est le but de l'instruction LEA?


676

Pour moi, cela ressemble à un MOV funky. Quel est son objectif et quand dois-je l'utiliser?


2
Voir aussi Utiliser LEA sur des valeurs qui ne sont pas des adresses / pointeurs? : LEA est juste une instruction shift-and-add. Il a probablement été ajouté au 8086 parce que le matériel est déjà là pour décoder et calculer les modes d'adressage, pas parce qu'il n'est "destiné" qu'à être utilisé avec des adresses. N'oubliez pas que les pointeurs ne sont que des entiers dans l'assemblage.
Peter Cordes

Réponses:


798

Comme d'autres l'ont souligné, LEA (adresse effective de chargement) est souvent utilisé comme «astuce» pour effectuer certains calculs, mais ce n'est pas son objectif principal. Le jeu d'instructions x86 a été conçu pour prendre en charge des langages de haut niveau comme Pascal et C, où les tableaux, en particulier les tableaux d'entiers ou de petites structures, sont courants. Considérons, par exemple, une structure représentant les coordonnées (x, y):

struct Point
{
     int xcoord;
     int ycoord;
};

Imaginez maintenant une déclaration comme:

int y = points[i].ycoord;

points[]est un tableau de Point. En supposant que la base du tableau est déjà dans EBXet que la variable iest dans EAXet xcoordet ycoordsont chacun 32 bits (ainsi ycoordest à l'offset 4 octets dans la structure), cette instruction peut être compilée pour:

MOV EDX, [EBX + 8*EAX + 4]    ; right side is "effective address"

qui atterrira ydans EDX. Le facteur d'échelle de 8 est dû au fait que chacun a une Pointtaille de 8 octets. Considérons maintenant la même expression utilisée avec l'opérateur "adresse de" &:

int *p = &points[i].ycoord;

Dans ce cas, vous ne voulez pas la valeur de ycoord, mais son adresse. C'est là que LEA(l'adresse effective de chargement) entre en jeu. Au lieu d'un MOV, le compilateur peut générer

LEA ESI, [EBX + 8*EAX + 4]

qui chargera l'adresse dans ESI.


112
N'aurait-il pas été plus propre d'étendre l' movinstruction et de laisser les crochets? MOV EDX, EBX + 8*EAX + 4
Natan Yellin

14
@imacake En remplaçant LEA par un MOV spécialisé, vous gardez la syntaxe propre: [] les crochets sont toujours l'équivalent de déréférencer un pointeur en C. Sans crochets, vous traitez toujours le pointeur lui-même.
Natan Yellin

139
Faire des calculs dans une instruction MOV (EBX + 8 * EAX + 4) n'est pas valide. LEA ESI, [EBX + 8 * EAX + 4] est valide car il s'agit d'un mode d'adressage pris en charge par x86. en.wikipedia.org/wiki/X86#Addressing_modes
Erik

30
@JonathanDickinson LEA est comme un MOVavec une source indirecte, sauf qu'il ne fait que l'indirection et non le MOV. Il ne lit pas réellement à partir de l'adresse calculée, la calcule simplement.
hobbs

24
Erik, le commentaire de la tournée n'est pas exact. MOV eax, [ebx + 8 * ecx + 4] est valide. Cependant, MOV renvoie le contenu de l'emplacement mémoire alors que LEA renvoie l'adresse
Olorin

562

Extrait du "Zen de l'Assemblée" d'Abrash:

LEA, la seule instruction qui effectue des calculs d'adressage de la mémoire mais ne traite pas réellement la mémoire. LEAaccepte un opérande d'adressage de mémoire standard, mais ne fait rien de plus que de stocker le décalage de mémoire calculé dans le registre spécifié, qui peut être n'importe quel registre à usage général.

Qu'est-ce que cela nous donne? Deux choses qui ADDne fournissent pas:

  1. la possibilité d'effectuer l'addition avec deux ou trois opérandes, et
  2. la possibilité de stocker le résultat dans n'importe quel registre; pas seulement l'un des opérandes source.

Et LEAne modifie pas les drapeaux.

Exemples

  • LEA EAX, [ EAX + EBX + 1234567 ]calcule EAX + EBX + 1234567(c'est trois opérandes)
  • LEA EAX, [ EBX + ECX ]calcule EBX + ECXsans remplacer non plus par le résultat.
  • multiplication par constante (par deux, trois, cinq ou neuf), si vous l'utilisez comme LEA EAX, [ EBX + N * EBX ](N peut être 1,2,4,8).

Autre cas d'utilisation est pratique dans les boucles: la différence entre LEA EAX, [ EAX + 1 ]et INC EAXest que le dernier change EFLAGSmais pas le premier; cela préserve l' CMPétat.


42
@AbidRahmanK quelques exemples: LEA EAX, [ EAX + EBX + 1234567 ]calcule la somme de EAX, EBXet 1234567(c'est trois opérandes). LEA EAX, [ EBX + ECX ]calcule EBX + ECX sans remplacer non plus par le résultat. La troisième chose LEAutilisée (non répertoriée par Frank) est la multiplication par constante (par deux, trois, cinq ou neuf), si vous l'utilisez comme LEA EAX, [ EBX + N * EBX ]( Npeut être 1,2,4,8). Autre cas d'utilisation est pratique dans les boucles: la différence entre LEA EAX, [ EAX + 1 ]et INC EAXest que le dernier change EFLAGSmais pas le premier; cela préserve l' CMPétat
FrankH.

@FrankH. Je ne comprends toujours pas, donc il charge un pointeur ailleurs?

6
@ ripDaddy69 oui, en quelque sorte - si par "charger" vous voulez dire "effectue le calcul d'adresse / l'arithmétique du pointeur". Il n'accède pas à la mémoire (c'est-à-dire qu'il ne "déréférence" pas le pointeur comme il serait appelé en termes de programmation C).
FrankH.

2
+1: Cela rend explicite les types de «trucs» qui LEApeuvent être utilisés ... (voir «LEA (adresse effective de chargement) est souvent utilisé comme« truc »pour effectuer certains calculs» dans la réponse populaire d'IJ Kennedy ci-dessus)
Assad Ebrahim

3
Il y a une grande différence entre 2 opérandes LEA qui est rapide et 3 opérandes LEA qui est lent. Le manuel d'optimisation Intel indique que la voie rapide LEA est à cycle unique et que la voie lente LEA prend trois cycles. De plus, sur Skylake, il existe deux unités fonctionnelles à voie rapide (ports 1 et 5) et il n'y a qu'une seule unité fonctionnelle à voie lente (port 1). La règle 33 de codage de l'assembleur / compilateur du manuel met en garde contre l'utilisation de 3 opérandes LEA.
Olsonist du

110

Une autre caractéristique importante de l' LEAinstruction est qu'elle ne modifie pas les codes de condition tels que CFet ZF, tout en calculant l'adresse par des instructions arithmétiques comme ADDou le MULfait. Cette fonctionnalité diminue le niveau de dépendance entre les instructions et laisse ainsi place à une optimisation supplémentaire par le compilateur ou le planificateur matériel.


1
Oui, il leaest parfois utile pour le compilateur (ou le codeur humain) de faire des calculs sans encombrer le résultat d'un indicateur. Mais ce lean'est pas plus rapide que add. La plupart des instructions x86 écrivent des drapeaux. Les implémentations x86 hautes performances doivent renommer EFLAGS ou éviter le risque d'écriture après écriture pour que le code normal s'exécute rapidement, donc les instructions qui évitent les écritures d'indicateur ne sont pas meilleures à cause de cela. (Les informations partielles sur les drapeaux peuvent créer des problèmes, voir les instructions INC vs ADD 1: est-ce important? )
Peter Cordes

2
@PeterCordes: Je déteste en parler ici, mais - suis-je seul à penser que cette nouvelle balise [x86-lea] est redondante et inutile?
Michael Petch

2
@MichaelPetch: Oui, je pense que c'est trop spécifique. Cela semble confondre les débutants qui ne comprennent pas le langage machine et que tout (y compris les pointeurs) ne sont que des bits / octets / entiers, donc il y a beaucoup de questions à ce sujet avec un grand nombre de votes. Mais avoir une étiquette pour cela implique qu'il y a de la place pour un nombre illimité de questions futures, alors qu'en fait il y en a environ 2 ou 3 qui ne sont pas seulement des doublons. (qu'est-ce que c'est? Comment l'utiliser pour multiplier des entiers? et comment il s'exécute en interne sur les AGU par rapport aux ALU et avec quelle latence / débit. Et c'est peut-être le but "prévu")
Peter Cordes

@PeterCordes: Je suis d'accord, et si quoi que ce soit, tous ces messages en cours de modification sont à peu près un double de quelques-unes des questions liées à LEA. Plutôt qu'une étiquette, tous les doublons doivent être identifiés et marqués à mon humble avis.
Michael Petch

1
@EvanCarroll: accrochez-vous au balisage de toutes les questions LEA, si vous n'avez pas encore terminé. Comme indiqué ci-dessus, nous pensons que x86-lea est trop spécifique pour une balise, et il n'y a pas beaucoup de place pour de futures questions non dupliquées. Je pense que ce serait beaucoup de travail de choisir en fait une «meilleure» Q&R comme cible de dup pour la plupart d'entre eux, ou de réellement décider lesquels obtenir des mods à fusionner.
Peter Cordes

93

Malgré toutes les explications, LEA est une opération arithmétique:

LEA Rt, [Rs1+a*Rs2+b] =>  Rt = Rs1 + a*Rs2 + b

C'est juste que son nom est extrêmement stupide pour une opération shift + add. La raison de cela a déjà été expliquée dans les réponses les mieux notées (c'est-à-dire qu'elle a été conçue pour mapper directement les références de mémoire de haut niveau).


8
Et que l'arithmétique est effectuée par le matériel de calcul d'adresse.
Ben Voigt

30
@BenVoigt J'avais l'habitude de dire cela, parce que je suis un vieux mec :-) Traditionnellement, les processeurs x86 utilisaient les unités d'adressage pour cela, d'accord. Mais la "séparation" est devenue très floue ces jours-ci. Certains processeurs n'ont plus du tout d' AGU dédiés , d'autres ont choisi de ne pas s'exécuter LEAsur les AGU mais sur les ALU entières ordinaires. Il faut lire les spécifications du CPU de très près ces jours-ci pour savoir "où ça se passe" ...
FrankH.

2
@FrankH .: les CPU hors service exécutent généralement LEA sur les ALU, tandis que certains CPU dans l'ordre (comme Atom) l'exécutent parfois sur les AGU (car ils ne peuvent pas être occupés à gérer un accès mémoire).
Peter Cordes du

3
Non, le nom n'est pas stupide. LEAvous donne l'adresse issue de tout mode d'adressage lié à la mémoire. Ce n'est pas une opération de décalage et d'ajout.
Kaz

3
FWIW il y a très peu (le cas échéant) de processeurs x86 actuels qui effectuent l'opération sur l'AGU. La plupart ou tous utilisent simplement un ALU comme n'importe quel autre op arithmétique.
BeeOnRope

77

Peut-être juste une autre chose à propos de l'enseignement LEA. Vous pouvez également utiliser LEA pour multiplier rapidement les registres par 3, 5 ou 9.

LEA EAX, [EAX * 2 + EAX]   ;EAX = EAX * 3
LEA EAX, [EAX * 4 + EAX]   ;EAX = EAX * 5
LEA EAX, [EAX * 8 + EAX]   ;EAX = EAX * 9

13
+1 pour l'astuce. Mais je voudrais poser une question (peut-être stupide), pourquoi ne pas multiplier directement par trois comme ça LEA EAX, [EAX*3]?
Abid Rahman K

13
@Abid Rahman K: Il n'y a rien de tel qu'un jeu d'instructions CPU x86.
GJ.

50
@AbidRahmanK malgré la syntaxe intel asm qui la fait ressembler à une multiplication, l'instruction lea ne peut coder que les opérations de décalage. L'opcode a 2 bits pour décrire le décalage, donc vous ne pouvez multiplier que par 1,2,4 ou 8.
ithkuil

6
@Koray Tugay: Vous pouvez utiliser le décalage vers la gauche comme shlinstruction pour multiplier les registres par 2,4,8,16 ... c'est plus rapide et plus court. Mais pour multiplier avec des nombres différents de puissance de 2, nous utilisons normalement l' mulinstruction qui est plus prétentieuse et plus lente.
GJ.

8
@GJ. bien qu'il n'y ait pas un tel encodage, certains assembleurs acceptent cela comme un raccourci, par exemple fasm. Donc, par exemple, lea eax,[eax*3]se traduirait par l'équivalent de lea eax,[eax+eax*2].
Ruslan

59

leaest l'abréviation de "load effective address". Il charge l'adresse de la référence d'emplacement par l'opérande source dans l'opérande de destination. Par exemple, vous pouvez l'utiliser pour:

lea ebx, [ebx+eax*8]

pour déplacer les éléments de ebxpointeur eaxplus loin (dans un tableau 64 bits / élément) avec une seule instruction. Fondamentalement, vous bénéficiez de modes d'adressage complexes pris en charge par l'architecture x86 pour manipuler efficacement les pointeurs.


23

La principale raison que vous utilisez LEAsur un MOVest si vous devez effectuer une arithmétique sur les registres que vous utilisez pour calculer l'adresse. En effet, vous pouvez effectuer ce qui équivaut à une arithmétique de pointeur sur plusieurs registres efficacement en combinaison "gratuitement".

Ce qui est vraiment déroutant, c'est que vous écrivez généralement un LEAcomme un, MOVmais que vous ne déréférez pas réellement la mémoire. En d'autres termes:

MOV EAX, [ESP+4]

Cela déplacera le contenu de ce ESP+4vers quoi pointe EAX.

LEA EAX, [EBX*8]

Cela déplacera l'adresse effective EBX * 8dans EAX, pas ce qui se trouve à cet endroit. Comme vous pouvez le voir, il est également possible de multiplier par des facteurs de deux (mise à l'échelle) tandis que a MOVse limite à ajouter / soustraire.


Désolé tout le monde. @ big.heart m'a dupé en donnant une réponse à cela il y a trois heures, en le faisant apparaître comme "nouveau" dans ma question à récurer.
David Hoelzer

1
Pourquoi la syntaxe utilise-t-elle des crochets lorsqu'elle ne fait pas d'adressage mémoire?
golopot

3
@ q4w56 C'est l'une de ces choses où la réponse est: "C'est comme ça que vous faites." Je crois que c'est l'une des raisons pour lesquelles les gens ont du mal à comprendre ce qui se LEApasse.
David Hoelzer

2
@ q4w56: c'est une instruction shift + add qui utilise la syntaxe d'opérande mémoire et l' encodage du code machine. Sur certains processeurs, il peut même utiliser le matériel AGU, mais c'est un détail historique. Le fait toujours pertinent est que le matériel du décodeur existe déjà pour décoder ce type de décalage + ajout, et LEA nous permet de l'utiliser pour l'arithmétique au lieu de l'adressage de la mémoire. (Ou pour les calculs d'adresse si une entrée est en fait un pointeur).
Peter Cordes

20

Le 8086 possède une grande famille d'instructions qui acceptent un opérande de registre et une adresse effective, effectuent des calculs pour calculer la partie décalée de cette adresse effective et effectuent une opération impliquant le registre et la mémoire référencés par l'adresse calculée. Il était assez simple que l'une des instructions de cette famille se comporte comme ci-dessus, sauf pour ignorer cette opération de mémoire réelle. Ceci, les instructions:

mov ax,[bx+si+5]
lea ax,[bx+si+5]

ont été mises en œuvre de manière presque identique en interne. La différence est une étape ignorée. Les deux instructions fonctionnent comme:

temp = fetched immediate operand (5)
temp += bx
temp += si
address_out = temp  (skipped for LEA)
trigger 16-bit read  (skipped for LEA)
temp = data_in  (skipped for LEA)
ax = temp

Quant à savoir pourquoi Intel pensait que cette instruction valait la peine d'être incluse, je ne suis pas exactement sûr, mais le fait qu'elle était bon marché à mettre en œuvre aurait été un facteur important. Un autre facteur aurait été le fait que l'assembleur d'Intel a permis de définir des symboles par rapport au registre BP. Si fnordétait défini comme un symbole relatif à BP (par exemple BP + 8), on pourrait dire:

mov ax,fnord  ; Equivalent to "mov ax,[BP+8]"

Si l'on voulait utiliser quelque chose comme stosw pour stocker des données vers une adresse relative à BP, pouvoir dire

mov ax,0 ; Data to store
mov cx,16 ; Number of words
lea di,fnord
rep movs fnord  ; Address is ignored EXCEPT to note that it's an SS-relative word ptr

était plus pratique que:

mov ax,0 ; Data to store
mov cx,16 ; Number of words
mov di,bp
add di,offset fnord (i.e. 8)
rep movs fnord  ; Address is ignored EXCEPT to note that it's an SS-relative word ptr

Notez que l'oubli du "décalage" mondial entraînerait l'ajout du contenu de l'emplacement [BP + 8], plutôt que la valeur 8, à DI. Oops.


12

Comme les réponses existantes mentionnées, LEAprésente les avantages d'effectuer l'arithmétique d'adressage de la mémoire sans accéder à la mémoire, en enregistrant le résultat arithmétique dans un registre différent au lieu de la forme simple d'une instruction d'ajout. Le véritable avantage sous-jacent des performances est que le processeur moderne dispose d'une unité et d'un port LEA ALU séparés pour la génération efficace d'adresses (y compris LEAet d'autres adresses de référence de mémoire), cela signifie que l'opération arithmétique dans LEAet toute autre opération arithmétique normale dans ALU pourrait être effectuée en parallèle en une seule coeur.

Consultez cet article de l'architecture Haswell pour plus de détails sur l'unité LEA: http://www.realworldtech.com/haswell-cpu/4/

Un autre point important qui n'est pas mentionné dans d'autres réponses est l' LEA REG, [MemoryAddress]instruction PIC (code indépendant de la position) qui code l'adresse relative du PC dans cette instruction pour référence MemoryAddress. Ceci est différent de celui MOV REG, MemoryAddressqui code l'adresse virtuelle relative et nécessite une relocalisation / correction dans les systèmes d'exploitation modernes (comme ASLR est une caractéristique commune). LEAPeut donc être utilisé pour convertir un tel non PIC en PIC.


2
La partie "LEA ALU séparée" est généralement fausse. Les processeurs modernes s'exécutent leasur une ou plusieurs des mêmes ALU qui exécutent d'autres instructions arithmétiques (mais généralement moins d'entre elles que les autres arithmétiques). Par exemple, le CPU Haswell mentionné peut exécuter addou la subplupart des autres opérations arithmétiques de base sur quatre ALU différentes , mais ne peut s'exécuter que leasur une (complexe lea) ou deux (simple lea). Plus important encore, ces deux leaALU capables sont simplement deux des quatre qui peuvent exécuter d'autres instructions, il n'y a donc aucun avantage de parallélisme comme revendiqué.
BeeOnRope

L'article que vous avez lié (correctement) montre que LEA est sur le même port qu'un ALU entier (add / sub / boolean) et l'unité MUL entière dans Haswell. (Et les ALU vectorielles, y compris FP ADD / MUL / FMA). L'unité LEA simple uniquement se trouve sur le port 5, qui exécute également ADD / SUB / autre, et des mélanges de vecteurs, et d'autres choses. La seule raison pour laquelle je ne vote pas en aval est que vous signalez l'utilisation du LEA relatif au RIP (pour x86-64 uniquement).
Peter Cordes

8

L'instruction LEA peut être utilisée pour éviter de longs calculs d'adresses effectives par le CPU. Si une adresse est utilisée à plusieurs reprises, il est plus efficace de la stocker dans un registre au lieu de calculer l'adresse effective à chaque fois qu'elle est utilisée.


Pas nécessairement sur les x86 modernes. La plupart des modes d'adressage ont le même coût, avec quelques mises en garde. Il [esi]est donc rarement moins cher que de dire [esi + 4200]et n'est que rarement moins cher que [esi + ecx*8 + 4200].
BeeOnRope

@BeeOnRope [esi]n'est pas moins cher que [esi + ecx*8 + 4200]. Mais pourquoi s'embêter à comparer? Ils ne sont pas équivalents. Si vous voulez que le premier désigne le même emplacement mémoire que le second, vous avez besoin d'instructions supplémentaires: vous devez ajouter à esila valeur ecxmultipliée par 8. Euh oh, la multiplication va encombrer vos drapeaux CPU! Ensuite, vous devez ajouter le 4200. Ces instructions supplémentaires ajoutent à la taille du code (prendre de l'espace dans le cache d'instructions, cycles à récupérer).
Kaz

2
@Kaz - Je pense que vous manquiez mon point (ou bien j'ai raté le point de l'OP). Ma compréhension est que l'OP dit que si vous allez utiliser quelque chose comme à [esi + 4200]plusieurs reprises dans une séquence d'instructions, il est préférable de charger d'abord l'adresse effective dans un registre et de l'utiliser. Par exemple, plutôt que d'écrire add eax, [esi + 4200]; add ebx, [esi + 4200]; add ecx, [esi + 4200], vous devriez préférer lea edi, [esi + 4200]; add eax, [edi]; add ebx, [edi]; add ecx, [edi], ce qui est rarement plus rapide. C'est du moins l'interprétation claire de cette réponse.
BeeOnRope

Donc, la raison pour laquelle je comparais [esi]et [esi + 4200](ou [esi + ecx*8 + 4200]c'est que c'est la simplification que l'OP propose (si je comprends bien): que N instructions avec la même adresse complexe sont transformées en N instructions avec un adressage simple (un reg), plus un lea, car l'adressage complexe prend du temps. En fait, il est plus lent même sur les x86 modernes, mais uniquement en termes de latence, ce qui ne semble pas avoir d'importance pour les instructions consécutives avec la même adresse.
BeeOnRope

1
Peut-être que vous relâchez la pression du registre, oui - mais l'inverse peut être le cas: si les registres avec lesquels vous avez généré l'adresse effective sont actifs, vous avez besoin d' un autre registre pour enregistrer le résultat, leace qui augmente la pression dans ce cas. En général, le stockage des intermédiaires est une cause de pression de registre, pas une solution - mais je pense que dans la plupart des situations, c'est un lavage. @Kaz
BeeOnRope

7

L'instruction LEA (Load Effective Address) est un moyen d'obtenir l'adresse qui provient de l'un des modes d'adressage mémoire du processeur Intel.

Autrement dit, si nous avons un mouvement de données comme celui-ci:

MOV EAX, <MEM-OPERAND>

il déplace le contenu de l'emplacement mémoire désigné dans le registre cible.

Si nous remplaçons le MOVpar LEA, alors l'adresse de l'emplacement mémoire est calculée exactement de la même manière par l' <MEM-OPERAND>expression d'adressage. Mais au lieu du contenu de l'emplacement mémoire, nous obtenons l'emplacement lui-même dans la destination.

LEAn'est pas une instruction arithmétique spécifique; c'est un moyen d'intercepter l'adresse effective issue de l'un des modes d'adressage mémoire du processeur.

Par exemple, nous pouvons utiliser LEAune simple adresse directe. Aucune arithmétique n'est impliquée du tout:

MOV EAX, GLOBALVAR   ; fetch the value of GLOBALVAR into EAX
LEA EAX, GLOBALVAR   ; fetch the address of GLOBALVAR into EAX.

C'est valable; nous pouvons le tester à l'invite Linux:

$ as
LEA 0, %eax
$ objdump -d a.out

a.out:     file format elf64-x86-64

Disassembly of section .text:

0000000000000000 <.text>:
   0:   8d 04 25 00 00 00 00    lea    0x0,%eax

Ici, il n'y a pas d'ajout de valeur mise à l'échelle, ni de décalage. Zero est déplacé dans EAX. Nous pourrions faire cela en utilisant MOV avec un opérande immédiat également.

C'est la raison pour laquelle les gens qui pensent que les crochets LEAsont superflus se trompent gravement; les crochets ne sont pas de la LEAsyntaxe mais font partie du mode d'adressage.

LEA est réel au niveau matériel. L'instruction générée code le mode d'adressage réel et le processeur l'exécute au point de calculer l'adresse. Il déplace ensuite cette adresse vers la destination au lieu de générer une référence de mémoire. (Étant donné que le calcul d'adresse d'un mode d'adressage dans toute autre instruction n'a aucun effet sur les drapeaux CPU, LEAn'a aucun effet sur les drapeaux CPU.)

Contrairement au chargement de la valeur à partir de l'adresse zéro:

$ as
movl 0, %eax
$ objdump -d a.out | grep mov
   0:   8b 04 25 00 00 00 00    mov    0x0,%eax

C'est un encodage très similaire, vous voyez? Juste le 8dde LEAa changé 8b.

Bien sûr, cet LEAencodage est plus long que le déplacement d'un zéro immédiat dans EAX:

$ as
movl $0, %eax
$ objdump -d a.out | grep mov
   0:   b8 00 00 00 00          mov    $0x0,%eax

Il n'y a aucune raison LEAd'exclure cette possibilité, simplement parce qu'il existe une alternative plus courte; c'est simplement une combinaison orthogonale avec les modes d'adressage disponibles.


6

Voici un exemple.

// compute parity of permutation from lexicographic index
int parity (int p)
{
  assert (p >= 0);
  int r = p, k = 1, d = 2;
  while (p >= k) {
    p /= d;
    d += (k << 2) + 6; // only one lea instruction
    k += 2;
    r ^= p;
  }
  return r & 1;
}

Avec -O (optimiser) comme option de compilation, gcc trouvera l'instruction lea pour la ligne de code indiquée.


6

Il semble que beaucoup de réponses soient déjà terminées, je voudrais ajouter un autre exemple de code pour montrer comment les instructions lea et move fonctionnent différemment lorsqu'elles ont le même format d'expression.

Pour faire une histoire courte, les instructions lea et mov peuvent toutes deux être utilisées avec les parenthèses entourant l'opérande src des instructions. Lorsqu'ils sont entourés de () , l'expression dans () est calculée de la même manière; cependant, deux instructions interpréteront la valeur calculée dans l'opérande src d'une manière différente.

Que l'expression soit utilisée avec lea ou mov, la valeur src est calculée comme ci-dessous.

D (Rb, Ri, S) => (Reg [Rb] + S * Reg [Ri] + D)

Cependant, lorsqu'elle est utilisée avec l'instruction mov, elle essaie d'accéder à la valeur pointée par l'adresse générée par l'expression ci-dessus et de la stocker dans la destination.

En revanche, lorsque l'instruction lea est exécutée avec l'expression ci-dessus, elle charge la valeur générée telle qu'elle est vers la destination.

Le code ci-dessous exécute l'instruction lea et l'instruction mov avec le même paramètre. Cependant, pour rattraper la différence, j'ai ajouté un gestionnaire de signal au niveau de l'utilisateur pour détecter l'erreur de segmentation causée par l'accès à une mauvaise adresse à la suite d'une instruction mov.

Exemple de code

#define _GNU_SOURCE 1  /* To pick up REG_RIP */
#include <stdio.h> 
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include <signal.h>


uint32_t
register_handler (uint32_t event, void (*handler)(int, siginfo_t*, void*))
{
        uint32_t ret = 0;
        struct sigaction act;

        memset(&act, 0, sizeof(act));
        act.sa_sigaction = handler;
        act.sa_flags = SA_SIGINFO;
        ret = sigaction(event, &act, NULL);
        return ret;
}

void
segfault_handler (int signum, siginfo_t *info, void *priv)
{
        ucontext_t *context = (ucontext_t *)(priv);
        uint64_t rip = (uint64_t)(context->uc_mcontext.gregs[REG_RIP]);
        uint64_t faulty_addr = (uint64_t)(info->si_addr);

        printf("inst at 0x%lx tries to access memory at %ld, but failed\n",
                rip,faulty_addr);
        exit(1);
}

int
main(void)
{
        int result_of_lea = 0;

        register_handler(SIGSEGV, segfault_handler);

        //initialize registers %eax = 1, %ebx = 2

        // the compiler will emit something like
           // mov $1, %eax
           // mov $2, %ebx
        // because of the input operands
        asm("lea 4(%%rbx, %%rax, 8), %%edx \t\n"
            :"=d" (result_of_lea)   // output in EDX
            : "a"(1), "b"(2)        // inputs in EAX and EBX
            : // no clobbers
         );

        //lea 4(rbx, rax, 8),%edx == lea (rbx + 8*rax + 4),%edx == lea(14),%edx
        printf("Result of lea instruction: %d\n", result_of_lea);

        asm volatile ("mov 4(%%rbx, %%rax, 8), %%edx"
                       :
                       : "a"(1), "b"(2)
                       : "edx"  // if it didn't segfault, it would write EDX
          );
}

Résultat d'exécution

Result of lea instruction: 14
inst at 0x4007b5 tries to access memory at 14, but failed

1
Décomposer votre asm en ligne en déclarations distinctes n'est pas sûr et vos listes de clobbers sont incomplètes. Le bloc basic-asm indique que le compilateur n'a pas de clobbers, mais il modifie en fait plusieurs registres. Vous pouvez également utiliser =dpour indiquer au compilateur que le résultat est dans EDX, en enregistrant a mov. Vous avez également omis une déclaration de clobber précoce sur la sortie. Cela démontre ce que vous essayez de démontrer, mais c'est aussi un mauvais exemple trompeur d'asm en ligne qui se brisera s'il est utilisé dans d'autres contextes. C'est une mauvaise chose pour une réponse de débordement de pile.
Peter Cordes

Si vous ne voulez pas écrire %%sur tous ces noms de registre dans Extended asm, utilisez des contraintes d'entrée. comme asm("lea 4(%%ebx, %%eax, 8), %%edx" : "=d"(result_of_lea) : "a"(1), "b"(2));. Laisser les registres d'initialisation du compilateur signifie que vous n'avez pas non plus à déclarer de clobbers. Vous compliquez les choses par xor-zeroing avant que mov-immediat n'écrase également le registre entier.
Peter Cordes du

@PeterCordes Merci, Peter, voulez-vous que je supprime cette réponse ou la modifie suite à vos commentaires?
Jaehyuk Lee

1
Si vous corrigez l'asm en ligne, cela ne fait aucun mal et peut-être un bon exemple concret pour les débutants qui ne comprennent pas les autres réponses. Pas besoin de supprimer, et c'est une solution facile comme je l'ai montré dans mon dernier commentaire. Je pense que cela vaudrait un vote positif si le mauvais exemple d'asm en ligne était corrigé en un "bon" exemple. (Je n'ai pas downvote)
Peter Cordes

1
Où est-ce que quelqu'un dit que ce mov 4(%ebx, %eax, 8), %edxn'est pas valide? Quoi qu'il en soit, oui, car movil serait logique d'écrire "a"(1ULL)pour dire au compilateur que vous avez une valeur 64 bits, et donc il doit s'assurer qu'il est étendu pour remplir tout le registre. En pratique, il sera toujours utilisé mov $1, %eax, car l'écriture d'EAX zéro s'étend dans RAX, sauf si vous avez une situation étrange de code environnant où le compilateur savait que RAX = 0xff00000001ou quelque chose. Pour lea, vous utilisez toujours une taille d'opérande 32 bits, de sorte que les bits élevés parasites dans les registres d'entrée n'ont aucun effet sur le résultat 32 bits.
Peter Cordes du

4

LEA: juste une instruction "arithmétique" ..

MOV transfère des données entre opérandes mais lea ne fait que calculer


LEA déplace évidemment les données; il a un opérande de destination. LEA ne calcule pas toujours; il calcule si l'adresse effective exprimée dans l'opérande source est calculée. LEA EAX, GLOBALVAR ne calcule pas; il déplace simplement l'adresse de GLOBALVAR dans EAX.
Kaz

@Kaz merci pour vos commentaires. ma source était "LEA (adresse effective de chargement) est essentiellement une instruction arithmétique - elle n'effectue aucun accès réel à la mémoire, mais est couramment utilisée pour calculer les adresses (bien que vous puissiez calculer des entiers à usage général avec elle)." du livre Eldad-Eilam page 149
le comptable

@Kaz: C'est pourquoi LEA est redondant lorsque l'adresse est déjà une constante de temps de liaison; utiliser à la mov eax, offset GLOBALVARplace. Vous pouvez utiliser LEA, mais sa taille de code est légèrement plus grande que mov r32, imm32et s'exécute sur moins de ports, car il passe toujours par le processus de calcul d'adresse . lea reg, symboln'est utile qu'en 64 bits pour un LEA relatif au RIP, lorsque vous avez besoin de PIC et / ou d'adresses en dehors des 32 bits bas. En code 32 ou 16 bits, il n'y a aucun avantage. LEA est une instruction arithmétique qui expose la capacité du CPU à décoder / calculer les modes d'adressage.
Peter Cordes

@Kaz: par le même argument, on pourrait dire que imul eax, edx, 1cela ne calcule pas: il copie simplement edx dans eax. Mais en réalité, il exécute vos données via le multiplicateur avec une latence de 3 cycles. Ou qui rorx eax, edx, 0ne fait que copier (faire pivoter de zéro).
Peter Cordes

@PeterCordes Mon point est que LEA EAX, GLOBALVAL et MOV EAX, GLOBALVAR saisissent simplement l'adresse à partir d'un opérande immédiat. Aucun multiplicateur de 1 ou décalage de 0 n'est appliqué; il pourrait en être ainsi au niveau du matériel, mais ce n'est pas vu dans le langage d'assemblage ou le jeu d'instructions.
Kaz

1

Toutes les instructions normales de "calcul" comme l'ajout de multiplication, exclusif ou définir les drapeaux d'état comme zéro, signe. Si vous utilisez une adresse compliquée, AX xor:= mem[0x333 +BX + 8*CX] les indicateurs sont définis en fonction de l'opération xor.

Maintenant, vous voudrez peut-être utiliser l'adresse plusieurs fois. Le chargement d'une telle adresse dans un registre n'est jamais destiné à définir des indicateurs de statut et, heureusement, ce n'est pas le cas. L'expression "adresse effective de chargement" en informe le programmeur. C'est de là que vient cette étrange expression.

Il est clair qu'une fois que le processeur est capable d'utiliser l'adresse compliquée pour traiter son contenu, il est capable de le calculer à d'autres fins. En effet, il peut être utilisé pour effectuer une transformation x <- 3*x+1dans une instruction. Il s'agit d'une règle générale dans la programmation d'assemblage: utilisez les instructions, mais cela secoue votre bateau. La seule chose qui compte est de savoir si la transformation particulière incarnée par l'instruction vous est utile.

Conclusion

MOV, X| T| AX'| R| BX|

et

LEA, AX'| [BX]

ont le même effet sur AX mais pas sur les drapeaux d'état. (Il s'agit de la notation ciasdis .)


"Il s'agit d'une règle générale dans la programmation d'assemblage: utilisez les instructions, mais cela secoue votre bateau." Je ne donnerais pas personnellement ce conseil, en raison de choses comme call lbl lbl: pop raxle «travail» technique comme moyen d'obtenir la valeur de rip, mais vous rendrez la prédiction de branche très malheureuse. Utilisez les instructions comme vous le souhaitez, mais ne soyez pas surpris si vous faites quelque chose de délicat et que cela a des conséquences que vous n'aviez pas
prévues

@ The6P4C C'est une mise en garde utile. Cependant, s'il n'y a pas d'alternative à rendre la prédiction de branche malheureuse, il faut y aller. Il existe une autre règle générale dans la programmation d'assemblage. Il peut y avoir d'autres façons de faire quelque chose et vous devez choisir judicieusement parmi les alternatives. Il y a des centaines de façons d'obtenir le contenu du registre BL dans le registre AL. Si le reste de RAX n'a ​​pas besoin d'être conservé, LEA peut être une option. Ne pas affecter les indicateurs peut être une bonne idée sur certains des milliers de types de processeurs x86. Groetjes Albert
Albert van der Horst

-1

Pardonnez-moi si quelqu'un l'a déjà mentionné, mais à l'époque de x86 où la segmentation de la mémoire était toujours pertinente, vous ne pouvez pas obtenir les mêmes résultats à partir de ces deux instructions:

LEA AX, DS:[0x1234]

et

LEA AX, CS:[0x1234]

1
L '"adresse effective" n'est que la partie "décalée" d'une seg:offpaire. LEA n'est pas affecté par la base du segment; ces deux instructions seront (inefficacement) mises 0x1234dans AX. x86 n'a malheureusement pas un moyen facile de calculer une adresse linéaire complète (base effective + segment) dans un registre ou une paire de registres.
Peter Cordes

@PeterCordes Très utile, merci de me corriger.
tzoz
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.