J'aimerais savoir quelle est la différence entre ces instructions:
MOV AX, [TABLE-ADDR]
et
LEA AX, [TABLE-ADDR]
J'aimerais savoir quelle est la différence entre ces instructions:
MOV AX, [TABLE-ADDR]
et
LEA AX, [TABLE-ADDR]
Réponses:
LEA
signifie charger l'adresse effectiveMOV
signifie valeur de chargeEn bref, LEA
charge un pointeur vers l'élément que vous adressez tandis que MOV charge la valeur réelle à cette adresse.
Le but de LEA
est 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.
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.
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 var
pour obtenir un mov-immédiat au lieu d'une charge.
mov eax, var
est une charge, identique à mov eax, [var]
, et vous devez utiliser mov eax, OFFSET var
pour utiliser une étiquette comme constante immédiate.
lea
c'est le pire choix, sauf en mode 64 bits pour l'adressage relatif RIP. mov r32, imm32
fonctionne 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 .
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.
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
leal TextLabel, LabelFromBssSegment
quand vous en avez. comme .bss .lcomm LabelFromBssSegment, 4
, tu devrais le faire movl $TextLabel, LabelFromBssSegment
, n'est-ce pas?
lea
nécessite une destination de registre, mais mov
peut avoir une imm32
source et une destination mémoire. Cette limitation n'est bien entendu pas spécifique à l'assembleur GNU.
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
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_addr
et 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.
lea
la version fonctionne également très bien si table_addr
est une variable locale, par exemple
some_procedure proc
local table_addr[64]:word
lea ax,table_addr
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 lea
opérations traitent l'utilisation des parenthèses différemment de la façon dontmov
.
Pensez à C. Disons que j'ai un tableau de long
ce 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 mov
et 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), %r9
et 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 * 8
dans le registre %rbp
. C'est-à-dire: obtenir la valeur dans le registre %rdi
et 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ù array
devient %rdi
et i
devient %rsi
et x
devient %rbp
. La 8
est 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 movq
correspondait à un déréférencement, l'utilisation de leaq
ici 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 leaq
change 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 %rdi
et 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 leaq
et movq
est que cela movq
fait un déréférencement et leaq
non. En fait, pour écrire la leaq
description, j'ai essentiellement copié + collé la description de movq
, puis supprimé "Trouver la valeur à cet emplacement".
Pour résumer: movq
vs. leaq
est 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 leaq
elles ne le sont pas et sont une syntaxe purement pratique.
[1] J'ai dit que quand array
est 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 p
pointant vers des valeurs de type T
. L'expression p + i
ne signifie pas "la position à p
plus d' i
octets". Au lieu de cela, l'expression signifie p + i
en fait "la position à p
plus d' i * sizeof(T)
octets".
La commodité de ceci est que pour obtenir "la valeur suivante", il suffit d'écrire p + 1
au 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 5
par 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 lea
fait 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, cc
avec l' -O2
optimisation 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
&
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), %eax
utilise 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.
%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.
%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.
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
lea
est 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.
Comme indiqué dans les autres réponses:
MOV
va saisir les données à l'adresse entre crochets et placer ces données dans l'opérande de destination.LEA
effectuera 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 LEA
est 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), LEA
est parfois utilisée pour ajouter ou multiplier des registres ensemble sans utiliser une instruction explicite ADD
ou 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 */
lea label, %eax
d'un [disp32]
mode d'adressage absolu . Utilisez mov $label, %eax
plutô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.
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 ).
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.
lea [label
est 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.
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.