Quelle est la différence entre MOV et LEA?


139

J'aimerais savoir quelle est la différence entre ces instructions:

MOV AX, [TABLE-ADDR]

et

LEA AX, [TABLE-ADDR]


8
merci nick. Tout d'abord, je n'aurais pas trouvé de réponse à cette question en examinant ce lien. Ici, je cherchais une info spécifique, la discussion dans le lien que vous avez fourni est de nature plus générale.
naveen

3
J'ai voté pour le dup de @ Nick il y a longtemps, mais je viens de vtc. A la réflexion, j'étais trop pressé et maintenant avec naveen que a) l'autre question ne répond pas "quelle est la différence" et b) c'est une question utile. Toutes mes excuses à naveen pour mon erreur - si seulement je pouvais annuler le vtc ...
Ruben Bartelink


En relation: Utiliser LEA sur des valeurs qui ne sont pas des adresses / pointeurs? parle d'autres utilisations de LEA, pour des mathématiques arbitraires.
Peter Cordes

Réponses:


169
  • LEA signifie charger l'adresse effective
  • MOV signifie valeur de charge

En bref, LEAcharge un pointeur vers l'élément que vous adressez tandis que MOV charge la valeur réelle à cette adresse.

Le but de LEAest de permettre d'effectuer un calcul d'adresse non trivial et de stocker le résultat [pour une utilisation ultérieure]

LEA ax, [BP+SI+5] ; Compute address of value

MOV ax, [BP+SI+5] ; Load value at that address

Là où il n'y a que des constantes impliquées, MOV(grâce aux calculs de constantes de l'assembleur) peuvent parfois sembler se chevaucher avec les cas les plus simples d'utilisation de LEA. C'est utile si vous avez un calcul en plusieurs parties avec plusieurs adresses de base, etc.


6
+1 merci pour l'explication claire, m'a aidé à répondre à une autre question.
legends2k

Cela me trouble que lea ait "load" dans le nom et les gens disent qu'il "charge" une adresse calculée dans un registre, car toutes les entrées pour calculer l'emplacement mémoire sont soit des valeurs immédiates, soit des registres. AFAICT lea n'effectue qu'un calcul, il ne charge rien, où charger signifie toucher la mémoire?
Joseph Garvin

2
@josephGarvin IIRC le terme fetch serait appliqué à cet aspect; La charge est juste la façon dont vous remplacez la valeur dans un registre par quelque chose de zéro. par exemple LAHF: charger les FLAGS dans le registre AH . Dans le CIL du CLR (qui est une machine abstraite basée sur une pile de plus haut niveau, le terme charge fait référence à la mise d'une valeur sur la pile notionnelle et est normalement l..., et l' séquivalent ... fait l'inverse). Ces notes: cs.umd.edu/class/sum2003/cmsc311/Notes/Mips/load.html ) suggèrent qu'il existe en effet des architectures où votre distinction s'applique.
Ruben Bartelink


45

Dans la syntaxe NASM:

mov eax, var       == lea eax, [var]   ; i.e. mov r32, imm32
lea eax, [var+16]  == mov eax, var+16
lea eax, [eax*4]   == shl eax, 2        ; but without setting flags

Dans la syntaxe MASM, utilisez OFFSET varpour obtenir un mov-immédiat au lieu d'une charge.


3
dans la syntaxe NASM uniquement. Dans la syntaxe MASM, mov eax, varest une charge, identique à mov eax, [var], et vous devez utiliser mov eax, OFFSET varpour utiliser une étiquette comme constante immédiate.
Peter Cordes

1
Clair, simple et démontre ce que j'essayais de confirmer. Merci.
JayArby

1
Notez que dans tous ces exemples, leac'est le pire choix, sauf en mode 64 bits pour l'adressage relatif RIP. mov r32, imm32fonctionne sur plus de ports. lea eax, [edx*4]est un copier-déplacer qui ne peut pas être fait dans une instruction autrement, mais dans le même registre LEA prend juste plus d'octets à encoder car il [eax*4]nécessite un disp32=0. (Il fonctionne sur des ports différents de ceux des shifts, cependant.) Voir agner.org/optimize et stackoverflow.com/tags/x86/info .
Peter Cordes

29

L'instruction MOV reg, addr signifie lire une variable stockée à l'adresse addr dans le registre reg. L'instruction LEA reg, addr signifie lire l'adresse (et non la variable stockée à l'adresse) dans le registre reg.

Une autre forme de l'instruction MOV est MOV reg, immdata qui signifie lire les données immédiates (c'est-à-dire constantes) immdata dans le registre reg. Notez que si l'adr dans LEA reg, addr est juste une constante (c'est-à-dire un offset fixe) alors cette instruction LEA est essentiellement exactement la même qu'une instruction MOV reg, immdata équivalente qui charge la même constante que les données immédiates.


11

Si vous ne spécifiez qu'un littéral, il n'y a aucune différence. LEA a plus de capacités, cependant, et vous pouvez en savoir plus ici:

http://www.oopweb.com/Assembly/Documents/ArtOfAssembly/Volume/Chapter_6/CH06-1.html#HEADING1-136


Je suppose, à l'exception que dans l'assembleur GNU, ce n'est pas vrai quand il s'agit d'étiquettes dans le segment .bss? AFAIR, vous ne pouvez pas vraiment leal TextLabel, LabelFromBssSegmentquand vous en avez. comme .bss .lcomm LabelFromBssSegment, 4, tu devrais le faire movl $TextLabel, LabelFromBssSegment, n'est-ce pas?
JSmyth

@JSmyth: C'est uniquement parce qu'il leanécessite une destination de registre, mais movpeut avoir une imm32source et une destination mémoire. Cette limitation n'est bien entendu pas spécifique à l'assembleur GNU.
Peter Cordes

1
En outre, cette réponse est fondamentalement erronée parce que la question porte sur MOV AX, [TABLE-ADDR], ce qui est une charge. Il y a donc une différence majeure. L'instruction équivalente estmov ax, OFFSET table_addr
Peter Cordes

10

Cela dépend de l'assembleur utilisé, car

mov ax,table_addr

dans MASM fonctionne comme

mov ax,word ptr[table_addr]

Donc, il charge les premiers octets de table_addret PAS le décalage vers table_addr. Vous devriez utiliser à la place

mov ax,offset table_addr

ou

lea ax,table_addr

qui fonctionne de la même manière.

leala version fonctionne également très bien si table_addrest une variable locale, par exemple

some_procedure proc

local table_addr[64]:word

lea ax,table_addr

merci beaucoup, c'est juste que je ne peux pas en marquer plus d'une comme réponse :(
naveen

5
La différence entre les instructions x86 MOV et LEA ne dépend certainement PAS de l'assembleur.
IJ Kennedy

4

Aucune des réponses précédentes n'est allée au fond de ma propre confusion, alors j'aimerais ajouter la mienne.

Ce qui me manquait, c'est que les leaopérations traitent l'utilisation des parenthèses différemment de la façon dontmov .

Pensez à C. Disons que j'ai un tableau de longce que j'appelle array. Maintenant, l'expression array[i]effectue un déréférencement, chargeant la valeur de la mémoire à l'adresse array + i * sizeof(long)[1].

D'un autre côté, considérez l'expression &array[i]. Celui-ci contient toujours la sous-expression array[i], mais aucun déréférencement n'est effectué! Le sens de array[i]a changé. Cela ne signifie plus effectuer une déférence mais agit plutôt comme une sorte de spécification , indiquant &quelle adresse mémoire nous recherchons. Si vous le souhaitez, vous pouvez également penser &à "annuler" la déréférence.

Parce que les deux cas d'utilisation sont similaires à bien des égards, ils partagent la syntaxe array[i], mais l'existence ou l'absence d'un &change la façon dont cette syntaxe est interprétée. Sans &, c'est une déréférence et lit réellement à partir du tableau. Avec &, ce n'est pas le cas. La valeur array + i * sizeof(long)est toujours calculée, mais elle n'est pas déréférencée.

La situation est très similaire avec movet lea. Avec mov, une déréférence se produit qui ne se produit pas avec lea. Ceci malgré l'utilisation de parenthèses qui se produit dans les deux. Par exemple, movq (%r8), %r9et leaq (%r8), %r9. Avec mov, ces parenthèses signifient "déréférencer"; avec lea, ils ne le font pas. Ceci est similaire à la façon array[i]dont signifie "déréférencer" uniquement lorsqu'il n'y a pas &.

Un exemple s'impose.

Considérez le code

movq (%rdi, %rsi, 8), %rbp

Cela charge la valeur à l'emplacement de mémoire %rdi + %rsi * 8dans le registre %rbp. C'est-à-dire: obtenir la valeur dans le registre %rdiet la valeur dans le registre %rsi. Multipliez ce dernier par 8, puis ajoutez-le au premier. Trouvez la valeur à cet endroit et placez-la dans le registre %rbp.

Ce code correspond à la ligne C x = array[i];, où arraydevient %rdiet idevient %rsiet xdevient %rbp. La 8est la longueur du type de données contenu dans le tableau.

Considérons maintenant un code similaire qui utilise lea:

leaq (%rdi, %rsi, 8), %rbp

Tout comme l'utilisation de movqcorrespondait à un déréférencement, l'utilisation de leaqici correspond à ne pas déréférencer. Cette ligne d'assemblage correspond à la ligne C x = &array[i];. Rappelez-vous que cela &change la signification array[i]du déréférencement à la simple spécification d'un emplacement. De même, l'utilisation de leaqchange la signification de(%rdi, %rsi, 8) du déréférencement à la spécification d'un emplacement.

La sémantique de cette ligne de code est la suivante: obtenir la valeur dans le registre %rdiet la valeur dans le registre %rsi. Multipliez ce dernier par 8, puis ajoutez-le au premier. Placez cette valeur dans le registre%rbp . Aucune charge mémoire n'est impliquée, juste des opérations arithmétiques [2].

Notez que la seule différence entre mes descriptions de leaqet movqest que cela movqfait un déréférencement et leaqnon. En fait, pour écrire la leaqdescription, j'ai essentiellement copié + collé la description de movq, puis supprimé "Trouver la valeur à cet emplacement".

Pour résumer: movqvs. leaqest délicat car ils traitent l'utilisation des parenthèses, comme dans (%rsi)et (%rdi, %rsi, 8), différemment. Dans movq(et toutes les autres instructions à l'exception de lea), ces parenthèses désignent un véritable déréférencement, alors que dans leaqelles ne le sont pas et sont une syntaxe purement pratique.


[1] J'ai dit que quand arrayest un tableau de long, l'expression array[i]charge la valeur de l'adresse array + i * sizeof(long). C'est vrai, mais il y a une subtilité qui doit être abordée. Si j'écris le code C

long x = array[5];

ce n'est pas la même chose que de taper

long x = *(array + 5 * sizeof(long));

Il semble que cela devrait être basé sur mes déclarations précédentes, mais ce n'est pas le cas.

Ce qui se passe, c'est que l'ajout de pointeur C a une astuce. Disons que j'ai un pointeur ppointant vers des valeurs de type T. L'expression p + ine signifie pas "la position à pplus d' ioctets". Au lieu de cela, l'expression signifie p + i en fait "la position à pplus d' i * sizeof(T)octets".

La commodité de ceci est que pour obtenir "la valeur suivante", il suffit d'écrire p + 1au lieu dep + 1 * sizeof(T) .

Cela signifie que le code C long x = array[5];est en fait équivalent à

long x = *(array + 5)

car C multipliera automatiquement le 5par sizeof(long).

Donc, dans le contexte de cette question StackOverflow, en quoi tout cela est-il pertinent? Cela signifie que lorsque je dis "l'adresse array + i * sizeof(long)", je ne veux pas dire que " array + i * sizeof(long)" doit être interprété comme une expression C. Je fais la multiplication par sizeof(long)moi-même afin de rendre ma réponse plus explicite, mais je comprends que pour cette raison, cette expression ne doit pas être lue comme C. Tout comme les mathématiques normales qui utilisent la syntaxe C.

[2] Note latérale: parce que tout ce que leafait ce sont des opérations arithmétiques, ses arguments n'ont pas à faire référence à des adresses valides. Pour cette raison, il est souvent utilisé pour effectuer de l'arithmétique pure sur des valeurs qui peuvent ne pas être destinées à être déréférencées. Par exemple, ccavec l' -O2optimisation se traduit

long f(long x) {
  return x * 5;
}

dans ce qui suit (lignes non pertinentes supprimées):

f:
  leaq (%rdi, %rdi, 4), %rax  # set %rax to %rdi + %rdi * 4
  ret

1
Oui, bonne explication, plus en détail que les autres réponses, et oui l' &opérateur de C est une bonne analogie. Il vaut peut-être la peine de souligner que LEA est le cas particulier, alors que MOV est comme toute autre instruction qui peut prendre une mémoire ou enregistrer un opérande. par exemple, add (%rdi), %eaxutilise simplement le mode d'adressage pour adresser la mémoire, identique à MOV. Aussi lié: Utiliser LEA sur des valeurs qui ne sont pas des adresses / pointeurs? va plus loin dans cette explication: LEA est la façon dont vous pouvez utiliser la prise en charge matérielle du CPU pour le calcul d'adresse pour effectuer des calculs arbitraires.
Peter Cordes

"obtenir la valeur à %rdi" - Ceci est formulé de façon étrange. Vous voulez dire que la valeur du registre rdi doit être utilisée. Votre utilisation de «at» semble signifier un déréférencement de mémoire là où il n'y en a pas.
ecm

@PeterCordes Merci! J'ai ajouté le fait qu'il s'agissait d'un cas particulier à la réponse.
Quelklef le

1
@ecm Bon point; Je n'ai pas remarqué cela. Je l'ai changé maintenant, merci! :)
Quelklef

Pour info, une formulation plus courte qui résout le problème signalé par ecm comprend: "la valeur de %rdi " ou "la valeur dans %rdi ". Votre «valeur dans le registre %rdi» est longue mais correcte, et pourrait peut-être aider quelqu'un qui a du mal à comprendre les registres par rapport à la mémoire.
Peter Cordes le

2

Fondamentalement ... "Passez à REG ... après l'avoir calculé ..." cela semble être aussi bien pour d'autres fins :)

si vous oubliez simplement que la valeur est un pointeur, vous pouvez l'utiliser pour les optimisations / minimisation du code ... quoi que ce soit ..

MOV EBX , 1
MOV ECX , 2

;//with 1 instruction you got result of 2 registers in 3rd one ...
LEA EAX , [EBX+ECX+5]

EAX = 8

à l'origine, ce serait:

MOV EAX, EBX
ADD EAX, ECX
ADD EAX, 5

Oui, leaest une instruction shift-and-add qui utilise le codage et la syntaxe de la machine à opérande mémoire, car le matériel sait déjà décoder ModR / M + SIB + disp0 / 8/32.
Peter Cordes

1

Comme indiqué dans les autres réponses:

  • MOVva saisir les données à l'adresse entre crochets et placer ces données dans l'opérande de destination.
  • LEAeffectuera le calcul de l'adresse entre crochets et placera cette adresse calculée dans l'opérande de destination. Cela se produit sans réellement sortir de la mémoire et obtenir les données. Le travail effectué par LEAest dans le calcul de "l'adresse effective".

Parce que la mémoire peut être adressée de plusieurs manières différentes (voir les exemples ci-dessous), LEAest parfois utilisée pour ajouter ou multiplier des registres ensemble sans utiliser une instruction explicite ADDou MUL(ou équivalent).

Puisque tout le monde montre des exemples dans la syntaxe Intel, en voici quelques-uns dans la syntaxe AT&T:

MOVL 16(%ebp), %eax       /* put long  at  ebp+16  into eax */
LEAL 16(%ebp), %eax       /* add 16 to ebp and store in eax */

MOVQ (%rdx,%rcx,8), %rax  /* put qword at  rcx*8 + rdx  into rax */
LEAQ (%rdx,%rcx,8), %rax  /* put value of "rcx*8 + rdx" into rax */

MOVW 5(%bp,%si), %ax      /* put word  at  si + bp + 5  into ax */
LEAW 5(%bp,%si), %ax      /* put value of "si + bp + 5" into ax */

MOVQ 16(%rip), %rax       /* put qword at rip + 16 into rax                 */
LEAQ 16(%rip), %rax       /* add 16 to instruction pointer and store in rax */

MOVL label(,1), %eax      /* put long at label into eax            */
LEAL label(,1), %eax      /* put the address of the label into eax */

Vous ne voulez jamais lea label, %eaxd'un [disp32]mode d'adressage absolu . Utilisez mov $label, %eaxplutôt. Oui, cela fonctionne, mais c'est moins efficace (code machine plus gros et fonctionne sur moins d'unités d'exécution). Depuis que vous mentionnez AT&T, utiliser LEA sur des valeurs qui ne sont pas des adresses / pointeurs? utilise AT&T, et ma réponse a quelques autres exemples AT&T.
Peter Cordes

1

Permet de comprendre cela avec un exemple.

mov eax, [ebx] et

lea eax, [ebx] Supposons que la valeur dans ebx soit 0x400000. Ensuite, mov ira à l'adresse 0x400000 et copiera 4 octets de données dans leur registre eax, tandis que lea copiera l'adresse 0x400000 dans eax. Ainsi, après l'exécution de chaque instruction, la valeur de eax sera dans chaque cas (en supposant que la mémoire 0x400000 contient 30).

eax = 30 (dans le cas de mov) eax = 0x400000 (dans le cas de lea) Pour la définition mov copiez les données de rm32 vers la destination (mov dest rm32) et lea (load effective address) copiera l'adresse vers la destination (mov dest rm32 ).


0

LEA (Load Effective Address) est une instruction shift-and-add. Il a été ajouté à 8086 car le matériel est là pour décoder et calculer les modes d'adressage.


0

MOV peut faire la même chose que LEA [étiquette], mais l'instruction MOV contient l'adresse effective à l'intérieur de l'instruction elle-même sous forme de constante immédiate (calculée à l'avance par l'assembleur). LEA utilise PC-relative pour calculer l'adresse effective pendant l'exécution de l'instruction.


Cela n'est vrai que pour le mode 64 bits (où l'adressage relatif au PC était nouveau); dans les autres modes lea [labelest un gaspillage inutile d'octets par rapport à un plus compact mov, vous devez donc spécifier les conditions dont vous parlez. En outre, pour certains assembleurs, [label]la syntaxe n'est pas appropriée pour un mode d'adressage relatif à RIP. Mais oui, c'est exact. Comment charger l'adresse de la fonction ou de l'étiquette dans le registre dans GNU Assembler explique plus en détail.
Peter Cordes le

-1

La différence est subtile mais importante. L'instruction MOV est un «MOVe» en fait une copie de l'adresse que représente l'étiquette TABLE-ADDR. L'instruction LEA est une 'adresse effective de chargement' qui est une instruction indirecte, ce qui signifie que TABLE-ADDR pointe vers un emplacement mémoire où l'adresse à charger est trouvée.

Utiliser efficacement LEA équivaut à utiliser des pointeurs dans des langages tels que C, en tant que tel, c'est une instruction puissante.


7
Je pense que cette réponse est au mieux déroutante. "L'instruction LEA est une 'Adresse effective de chargement' qui est une instruction indirecte, ce qui signifie que TABLE-ADDR pointe vers un emplacement mémoire où l'adresse à charger est trouvée." En fait, LEA chargera l'adresse, pas le contenu de l'adresse. Je pense en fait que l'interlocuteur doit être rassuré sur le fait que MOV et LEA peuvent se chevaucher et faire exactement la même chose, dans certaines circonstances
Bill Forster
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.