Quel est le but de L'instruction LEA?
pour moi, ça ressemble à un funky MOV. Quel est son but et quand dois-je utiliser?
14 réponses
comme d'autres l'ont souligné, LEA (load effective address) est souvent utilisé comme un" truc " pour faire certains calculs, mais ce n'est pas son but principal. L'ensemble d'instruction 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'ints ou de petites structures-sont communs. 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;
où points[]
est un tableau de Point
. En supposant que la base du tableau est déjà dans EBX
, et la variable i
est dans EAX
, et xcoord
et ycoord
sont chacun 32 bits (donc ycoord
est à l'offset 4 octets dans la structure), cette déclaration peut être compilée à:
MOV EDX, [EBX + 8*EAX + 4] ; right side is "effective address"
qui atterrira y
dans EDX
. Le facteur d'échelle de 8 est parce que chaque Point
est de 8 octets. Considérons maintenant la même expression utilisée avec "adresse de" operator&:
int *p = &points[i].ycoord;
Dans ce cas, vous ne voulez pas la valeur de ycoord
, mais son adresse. C'est là que LEA
(charge efficace de l'adresse). Au lieu d'un MOV
, le compilateur peut générer
LEA ESI, [EBX + 8*EAX + 4]
qui chargera l'adresse dans ESI
.
De la "Zen" Assemblage par Abrash:
LEA
, la seule instruction qui effectue des calculs d'adressage de mémoire, mais ne s'adresse pas réellement à la mémoire.LEA
accepte un opérande d'adressage de mémoire standard, mais ne fait rien de plus que de stocker l'offset de mémoire calculé dans le registre spécifié, qui peut être n'importe quel registre à usage général.Qu'est-ce que ça nous donne? Deux choses que
ADD
ne fournit pas:
- la capacité d'effectuer l'addition avec deux ou trois opérandes, et
- la capacité de stocker le résultat dans n'importe quel Registre; pas seulement un des opérandes source.
Et LEA
ne modifie pas les drapeaux.
exemples
-
LEA EAX, [ EAX + EBX + 1234567 ]
calculeEAX + EBX + 1234567
(c'est-à-dire trois opérandes) -
LEA EAX, [ EBX + ECX ]
calculeEBX + ECX
sans passer 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 usecase est pratique dans les boucles: la différence entre LEA EAX, [ EAX + 1 ]
et INC EAX
est que ce dernier change EFLAGS
mais le premier non; cela préserve l'état CMP
.
une autre caractéristique importante de l'instruction LEA
est qu'elle ne modifie pas les codes de condition tels que CF
et ZF
, alors que le calcul de l'adresse par des instructions arithmétiques comme ADD
ou MUL
le fait. Cette fonctionnalité diminue le niveau de dépendance entre les instructions et permet ainsi une optimisation supplémentaire par le compilateur ou l'ordonnanceur matériel.
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 en a déjà été expliquée dans les réponses les mieux cotées (c.-à-d. qu'elle a été conçue pour cartographier directement les références de mémoire de haut niveau).
peut-être juste une autre chose à propos de Lea instruction. Vous pouvez également utiliser LEA pour la multiplication rapide des 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
lea
est une abréviation de "load effective address". Il charge l'adresse de la référence de localisation par l'opérande source vers l'opérande de destination. Par exemple, vous pouvez l'utiliser pour:
lea ebx, [ebx+eax*8]
déplacer ebx
pointeur eax
éléments supplémentaires (en 64 bits/élément de tableau) avec une seule instruction. Fondamentalement, vous bénéficiez de modes d'adressage complexes supportés par l'architecture x86 pour manipuler efficacement les pointeurs.
La principale raison pour laquelle vous utilisez LEA
plus d'un MOV
est si vous devez effectuer une opération arithmétique sur les registres que vous utilisez pour calculer l'adresse. En effet, vous pouvez effectuer ce qui se résume à pointer arithmétique sur plusieurs des registres en combinaison efficacement pour "gratuit."
ce qui est vraiment déroutant à ce sujet est que vous écrivez typiquement un LEA
comme un MOV
mais vous n'êtes pas réellement déréférencement de la mémoire. En d'autres termes:
MOV EAX, [ESP+4]
cela déplacera le contenu de ce que ESP+4
pointe dans EAX
.
LEA EAX, [EBX*8]
cela déplacera l'adresse effective EBX * 8
dans EAX, pas ce qui est trouvé dans cet endroit. Comme vous pouvez le voir, aussi, il est possible de multiplier par des facteurs de deux (échelle) alors qu'un MOV
est limité à ajouter/soustraire.
le 8086 a une grande famille d'instructions qui acceptent un opérande de registre et une adresse effective, effectuent quelques calculs pour calculer la partie offset de cette adresse effective, et effectuent une certaine opération impliquant le registre et la mémoire mentionnée par l'adresse calculée. Il était assez simple d'avoir une des instructions dans cette famille se comporter comme ci-dessus, sauf pour sauter cette opération mémoire réelle. Ceci, les instructions:
mov ax,[bx+si+5]
lea ax,[bx+si+5]
ont été mis en œuvre de façon presque identique à l'interne. La différence est une étape sautée. Les deux instructions fonctionnent quelque chose 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 en valait la peine, Je ne suis pas vraiment sûr, mais le fait qu'elle soit peu coûteuse à mettre en œuvre aurait été un facteur important. Un autre facteur aurait été le fait que L'assembleur Intel permettait de définir des symboles par rapport au registre BP. Si fnord
a été défini comme un BP-symbole relatif (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 à une adresse relative BP, être en mesure de 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 commode 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
noter qu'oublier le monde "offset" ferait en sorte que le contenu de l'emplacement [BP+8], plutôt que la valeur 8, soit ajouté à DI. Oops.
comme les réponses existantes l'ont mentionné, LEA
a les avantages d'effectuer l'arithmétique d'adressage de mémoire sans accéder à la mémoire, enregistrant le résultat arithmétique à un registre différent au lieu de la forme simple de l'instruction d'ajout. Le véritable avantage de performance sous-jacent est que le processeur moderne dispose d'une unité et d'un port LEA ALU séparés pour la génération d'adresse efficace (y compris LEA
et autre adresse de référence de mémoire), ce qui signifie l'opération arithmétique en LEA
et d'autres opérations arithmétiques normales dans L'ALU pourraient être effectuées en parallèle dans un noyau.
voir cet article de Haswell architecture 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'instruction LEA REG, [MemoryAddress]
PIC (code indépendant de la position) qui Code L'adresse relative du PC dans cette instruction à la référence MemoryAddress
. Ceci est différent de MOV REG, MemoryAddress
qui Code l'adresse virtuelle relative et nécessite le déplacement/correction dans les systèmes d'exploitation modernes (comme ASLR est caractéristique commune). Ainsi LEA
peut être utilisé pour convertir un tel non PIC en PIC.
l'instruction LEA peut être utilisée pour éviter les calculs chronophages des adresses effectives par le CPU. Si une adresse est utilisée de façon répétée, il est plus efficace de la stocker dans un registre au lieu de calculer l'adresse effective chaque fois qu'elle est utilisée.
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 l'option-O (optimize) comme compilateur, gcc trouvera l'instruction lea pour la ligne de code indiquée.
l'instruction LEA (Load Effective Address) permet d'obtenir l'adresse qui provient de l'un des modes d'adressage mémoire du processeur Intel.
, C'est-à-dire, si nous avons un déplacement des données comme ceci:
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 MOV
par LEA
, alors l'adresse de l'emplacement de la mémoire est calculé exactement de la même façon par l'expression d'adressage <MEM-OPERAND>
. Mais au lieu de le contenu de l'emplacement mémoire, nous obtenons l'emplacement lui-même dans la destination.
LEA
n'est pas une instruction arithmétique spécifique; c'est un moyen d'intercepter l'adresse effective provenant de l'un des modes d'adressage mémoire du processeur.
par exemple, nous pouvons utiliser LEA
sur une simple adresse directe. Aucune arithmétique n'est impliqué du tout:
MOV EAX, GLOBALVAR ; fetch the value of GLOBALVAR into EAX
LEA EAX, GLOBALVAR ; fetch the address of GLOBALVAR into EAX.
ceci est valide; 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'addition d'une valeur d'échelle, ni d'offset. Zéro est déplacé dans EAX. Nous pourrions le faire en utilisant MOV avec une opérande immédiate aussi.
C'est la raison pour laquelle les gens qui pensent que les crochets dans LEA
sont superflus se trompent gravement; les crochets ne sont pas LEA
syntaxe mais sont une partie de la 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. Puis il déplace cette adresse vers la destination au lieu de générer une référence de mémoire. (Puisque le calcul d'adresse d'un mode d'adressage dans toute autre instruction n'a aucun effet sur les pavillons CPU, LEA
n'a aucun effet sur les pavillons CPU.)
Contraste avec le chargement de la valeur 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 8d
de LEA
a changé en 8b
.
bien sûr, ce codage LEA
est plus long que le déplacement d'un zéro immédiat dans EAX
:
$ as
movl "151940920", %eax
$ objdump -d a.out | grep mov
0: b8 00 00 00 00 mov "151940920"x0,%eax
il n'y a pas de raison pour LEA
d'exclure cette possibilité bien que juste parce qu'il y a une alternative plus courte; il est combinaison orthogonale avec les modes d'adressage disponibles.
LEA: juste une instruction "arithmétique"..
MOV transfère des données entre opérandes mais lea ne fait que calculer
il parce qu'à la place vous écrivez le code
mov dx,offset something
vous pouvez simplement écrire
lea dx,something