Pour moi, cela ressemble à un MOV funky. Quel est son objectif et quand dois-je l'utiliser?
Pour moi, cela ressemble à un MOV funky. Quel est son objectif et quand dois-je l'utiliser?
Réponses:
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;
où points[]
est un tableau de Point
. En supposant que la base du tableau est déjà dans EBX
et que la variable i
est dans EAX
et xcoord
et ycoord
sont chacun 32 bits (ainsi ycoord
est à 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 y
dans EDX
. Le facteur d'échelle de 8 est dû au fait que chacun a une Point
taille 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
.
mov
instruction et de laisser les crochets? MOV EDX, EBX + 8*EAX + 4
MOV
avec 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.
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.LEA
accepte 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
ADD
ne fournissent pas:
- la possibilité d'effectuer l'addition avec deux ou trois opérandes, et
- la possibilité de stocker le résultat dans n'importe quel registre; pas seulement l'un des opérandes source.
Et LEA
ne 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 + ECX
sans remplacer non plus par le résultat.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 EAX
est que le dernier change EFLAGS
mais pas le premier; cela préserve l' CMP
état.
LEA EAX, [ EAX + EBX + 1234567 ]
calcule la somme de EAX
, EBX
et 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 LEA
utilisé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 ]
( 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 EAX
est que le dernier change EFLAGS
mais pas le premier; cela préserve l' CMP
état
LEA
peuvent ê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)
Une autre caractéristique importante de l' LEA
instruction est qu'elle ne modifie pas les codes de condition tels que CF
et ZF
, tout en calculant l'adresse par des instructions arithmétiques comme ADD
ou le MUL
fait. 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.
lea
est parfois utile pour le compilateur (ou le codeur humain) de faire des calculs sans encombrer le résultat d'un indicateur. Mais ce lea
n'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? )
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).
LEA
sur 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" ...
LEA
vous 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.
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
LEA EAX, [EAX*3]
?
shl
instruction 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' mul
instruction qui est plus prétentieuse et plus lente.
lea eax,[eax*3]
se traduirait par l'équivalent de lea eax,[eax+eax*2]
.
lea
est 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 ebx
pointeur eax
plus 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.
La principale raison que vous utilisez LEA
sur un MOV
est 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 LEA
comme un, MOV
mais 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+4
vers quoi pointe EAX
.
LEA EAX, [EBX*8]
Cela déplacera l'adresse effective EBX * 8
dans 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 MOV
se limite à ajouter / soustraire.
LEA
passe.
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.
Comme les réponses existantes mentionnées, LEA
pré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 LEA
et d'autres adresses de référence de mémoire), cela signifie que l'opération arithmétique dans LEA
et 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, MemoryAddress
qui 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). LEA
Peut donc être utilisé pour convertir un tel non PIC en PIC.
lea
sur 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 add
ou la sub
plupart des autres opérations arithmétiques de base sur quatre ALU différentes , mais ne peut s'exécuter que lea
sur une (complexe lea
) ou deux (simple lea
). Plus important encore, ces deux lea
ALU 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é.
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.
[esi]
est donc rarement moins cher que de dire [esi + 4200]
et n'est que rarement moins cher que [esi + ecx*8 + 4200]
.
[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 à esi
la valeur ecx
multiplié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).
[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.
[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.
lea
ce 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
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 MOV
par 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.
LEA
n'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 LEA
une 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 LEA
sont superflus se trompent gravement; les crochets ne sont pas de la LEA
syntaxe 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, LEA
n'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 8d
de LEA
a changé 8b
.
Bien sûr, cet LEA
encodage 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 LEA
d'exclure cette possibilité, simplement parce qu'il existe une alternative plus courte; c'est simplement une combinaison orthogonale avec les modes d'adressage disponibles.
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.
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
=d
pour 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.
%%
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.
mov 4(%ebx, %eax, 8), %edx
n'est pas valide? Quoi qu'il en soit, oui, car mov
il 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 = 0xff00000001
ou 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.
LEA: juste une instruction "arithmétique" ..
MOV transfère des données entre opérandes mais lea ne fait que calculer
mov eax, offset GLOBALVAR
place. Vous pouvez utiliser LEA, mais sa taille de code est légèrement plus grande que mov r32, imm32
et s'exécute sur moins de ports, car il passe toujours par le processus de calcul d'adresse . lea reg, symbol
n'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.
imul eax, edx, 1
cela 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, 0
ne fait que copier (faire pivoter de zéro).
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+1
dans 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 .)
call lbl
lbl: pop rax
le «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
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]
seg:off
paire. LEA n'est pas affecté par la base du segment; ces deux instructions seront (inefficacement) mises 0x1234
dans 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.