Per me, sembra solo un MOV funky. Qual è il suo scopo e quando dovrei usarlo?
Per me, sembra solo un MOV funky. Qual è il suo scopo e quando dovrei usarlo?
Risposte:
Come altri hanno sottolineato, il LEA (indirizzo efficace per il caricamento) viene spesso utilizzato come "trucco" per eseguire determinati calcoli, ma questo non è il suo scopo principale. Il set di istruzioni x86 è stato progettato per supportare linguaggi di alto livello come Pascal e C, dove le matrici - in particolare matrici di ints o piccole strutture - sono comuni. Considera, ad esempio, una struttura che rappresenta le coordinate (x, y):
struct Point
{
int xcoord;
int ycoord;
};
Ora immagina un'affermazione come:
int y = points[i].ycoord;
dove points[]
è un array di Point
. Supponendo che la base dell'array è già EBX
, e variabile i
è in EAX
, e xcoord
e ycoord
sono ogni 32 bit (così ycoord
viene a compensare 4 byte nel struct), questa affermazione può essere compilato a:
MOV EDX, [EBX + 8*EAX + 4] ; right side is "effective address"
, che si terra y
in EDX
. Il fattore di scala di 8 è perché ognuno ha Point
una dimensione di 8 byte. Consideriamo ora la stessa espressione utilizzata con l'operatore "address of" &:
int *p = &points[i].ycoord;
In questo caso, non vuoi il valore di ycoord
, ma il suo indirizzo. È qui che LEA
entra in gioco (indirizzo effettivo di caricamento). Invece di a MOV
, il compilatore può generare
LEA ESI, [EBX + 8*EAX + 4]
che caricherà l'indirizzo ESI
.
mov
istruzioni e lasciare le parentesi? MOV EDX, EBX + 8*EAX + 4
MOV
con una fonte indiretta, tranne che fa solo il riferimento indiretto e non il MOV
. In realtà non legge dall'indirizzo calcolato, ma solo lo calcola.
Dallo "Zen of Assembly" di Abrash:
LEA
, l'unica istruzione che esegue calcoli di indirizzamento della memoria ma in realtà non si occupa della memoria.LEA
accetta un operando di indirizzamento di memoria standard, ma non fa altro che memorizzare l'offset di memoria calcolato nel registro specificato, che può essere qualsiasi registro per scopi generici.Cosa ci dà questo? Due cose che
ADD
non forniscono:
- la capacità di eseguire addizioni con due o tre operandi e
- la capacità di memorizzare il risultato in qualsiasi registro; non solo uno degli operandi di origine.
E LEA
non altera le bandiere.
Esempi
LEA EAX, [ EAX + EBX + 1234567 ]
calcola EAX + EBX + 1234567
(sono tre operandi)LEA EAX, [ EBX + ECX ]
calcola EBX + ECX
senza ignorare neanche con il risultato.LEA EAX, [ EBX + N * EBX ]
(N può essere 1,2,4,8).Un altro caso d'uso è utile nei loop: la differenza tra LEA EAX, [ EAX + 1 ]
ed INC EAX
è che quest'ultimo cambia EFLAGS
ma il primo no; questo preserva lo CMP
stato.
LEA EAX, [ EAX + EBX + 1234567 ]
calcola la somma di EAX
, EBX
e 1234567
(cioè tre operandi). LEA EAX, [ EBX + ECX ]
calcola EBX + ECX
senza ignorare neanche con il risultato. La terza cosa LEA
usata (non elencata da Frank) è la moltiplicazione per costante (per due, tre, cinque o nove), se la usi come LEA EAX, [ EBX + N * EBX ]
( N
può essere 1,2,4,8). Un altro caso d'uso è utile nei loop: la differenza tra LEA EAX, [ EAX + 1 ]
ed INC EAX
è che quest'ultimo cambia EFLAGS
ma il primo no; questo preserva lo CMP
stato
LEA
possono essere usati per ... (vedi "LEA (indirizzo di caricamento efficace) è spesso usato come un" trucco "per fare determinati calcoli" nella risposta popolare di IJ Kennedy sopra)
Un'altra caratteristica importante LEA
dell'istruzione è che non altera i codici di condizione come CF
e ZF
, mentre calcola l'indirizzo con istruzioni aritmetiche come ADD
o MUL
fa. Questa funzione riduce il livello di dipendenza tra le istruzioni e fa quindi spazio a ulteriori ottimizzazioni da parte del compilatore o del programmatore hardware.
lea
volte è utile per il compilatore (o codificatore umano) fare matematica senza ostruire un risultato di bandiera. Ma lea
non è più veloce di add
. La maggior parte delle istruzioni x86 scrive flag. Le implementazioni x86 ad alte prestazioni devono rinominare EFLAGS o altrimenti evitare il rischio di scrittura dopo scrittura per l'esecuzione rapida del codice normale, quindi le istruzioni che evitano la scrittura di flag non sono migliori a causa di ciò. (I contenuti di flag parziali possono creare problemi, vedere le istruzioni INC vs ADD 1: importa? )
Nonostante tutte le spiegazioni, LEA è un'operazione aritmetica:
LEA Rt, [Rs1+a*Rs2+b] => Rt = Rs1 + a*Rs2 + b
È solo che il suo nome è estremamente stupido per un turno + aggiungi operazione. Il motivo era già stato spiegato nelle risposte più votate (ovvero è stato progettato per mappare direttamente i riferimenti di memoria di alto livello).
LEA
sulle AGU ma sulle ALU intere ordinarie. Bisogna leggere molto attentamente le specifiche della CPU in questi giorni per scoprire "dove vanno le cose" ...
LEA
ti dà l'indirizzo che deriva da qualsiasi modalità di indirizzamento legata alla memoria. Non è un turno e aggiungi un'operazione.
Forse solo un'altra cosa sull'istruzione LEA. Puoi anche utilizzare LEA per i registri a moltiplicazione rapida per 3, 5 o 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
istruzione per moltiplicare i registri per 2,4,8,16 ... è più veloce e più breve. Ma per moltiplicare con numeri diversi di potenza di 2 usiamo normalmente mul
istruzioni che sono più pretenziose e più lente.
lea eax,[eax*3]
si tradurrebbe in equivalente di lea eax,[eax+eax*2]
.
lea
è un'abbreviazione di "indirizzo di caricamento efficace". Carica l'indirizzo del riferimento di posizione dall'operando di origine nell'operando di destinazione. Ad esempio, puoi usarlo per:
lea ebx, [ebx+eax*8]
per spostare ulteriormente gli elementi del ebx
puntatore eax
(in un array a 64 bit / elemento) con una singola istruzione. Fondamentalmente, beneficiate di modalità di indirizzamento complesse supportate dall'architettura x86 per manipolare i puntatori in modo efficiente.
Il motivo principale che si utilizza LEA
su un MOV
è se è necessario eseguire l'aritmetica sui registri che si sta utilizzando per calcolare l'indirizzo. In effetti, è possibile eseguire ciò che equivale all'aritmetica del puntatore su molti dei registri in combinazione in modo efficace "gratis".
La cosa davvero confusa al riguardo è che in genere scrivi un LEA
tipo come un MOV
ma in realtà non stai dereferenziando la memoria. In altre parole:
MOV EAX, [ESP+4]
Ciò sposterà il contenuto di ciò a cui ESP+4
punta EAX
.
LEA EAX, [EBX*8]
Questo sposterà l'indirizzo effettivo EBX * 8
in EAX, non quello che si trova in quella posizione. Come puoi vedere, inoltre, è possibile moltiplicare per due fattori (ridimensionamento) mentre a MOV
è limitato all'aggiunta / sottrazione.
LEA
fa.
L'8086 ha una vasta famiglia di istruzioni che accetta un operando di registro e un indirizzo effettivo, esegue alcuni calcoli per calcolare la parte offset di quell'indirizzo effettivo ed esegue alcune operazioni che coinvolgono il registro e la memoria a cui fa riferimento l'indirizzo calcolato. Era abbastanza semplice che una delle istruzioni di quella famiglia si comportasse come sopra, tranne che per saltare quell'effettiva operazione di memoria. Questo, le istruzioni:
mov ax,[bx+si+5]
lea ax,[bx+si+5]
sono stati implementati quasi identicamente internamente. La differenza è un passo saltato. Entrambe le istruzioni funzionano in questo modo:
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
Per quanto riguarda il motivo per cui Intel pensava che valesse la pena includere queste istruzioni, non ne sono esattamente sicuro, ma il fatto che fosse economico da implementare sarebbe stato un grande fattore. Un altro fattore sarebbe stato il fatto che l'assemblatore Intel consentiva di definire simboli relativi al registro BP. Se fnord
fosse definito come un simbolo relativo alla BP (es. BP + 8), si potrebbe dire:
mov ax,fnord ; Equivalent to "mov ax,[BP+8]"
Se uno volesse usare qualcosa come stosw per memorizzare i dati in un indirizzo relativo alla BP, potendo 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
era più conveniente di:
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
Si noti che dimenticare l'offset del mondo causerebbe l'aggiunta a DI del contenuto della posizione [BP + 8], anziché del valore 8. Ops.
Come indicato nelle risposte esistenti, LEA
presenta i vantaggi di eseguire l'aritmetica di indirizzamento della memoria senza accedere alla memoria, salvando il risultato aritmetico in un registro diverso anziché nella semplice forma di istruzione add. Il vero vantaggio prestazionale sottostante è che il moderno processore ha un'unità LEA ALU separata e una porta per un'efficace generazione di indirizzi (compresi LEA
e altri indirizzi di riferimento di memoria), ciò significa che l'operazione aritmetica in LEA
e altre normali operazioni aritmetiche in ALU potrebbero essere eseguite in parallelo in una nucleo.
Consulta questo articolo sull'architettura Haswell per alcuni dettagli sull'unità LEA: http://www.realworldtech.com/haswell-cpu/4/
Un altro punto importante che non è menzionato in altre risposte è l' LEA REG, [MemoryAddress]
istruzione PIC (codice indipendente dalla posizione) che codifica l'indirizzo relativo del PC in questa istruzione come riferimento MemoryAddress
. Questo è diverso dal MOV REG, MemoryAddress
quale codifica l'indirizzo virtuale relativo e richiede il trasferimento / patch nei moderni sistemi operativi (come ASLR è una caratteristica comune). Quindi LEA
può essere utilizzato per convertire tali non PIC in PIC.
lea
su una o più delle stesse ALU che eseguono altre istruzioni aritmetiche (ma generalmente meno di quelle di altre aritmetiche). Ad esempio, la CPU Haswell menzionata può eseguire add
o la sub
maggior parte delle altre operazioni aritmetiche di base su quattro ALU diverse , ma può essere eseguita solo lea
su una (complessa lea
) o due (semplice lea
). Ancora più importante, questi lea
ALU a due capacità sono semplicemente due dei quattro che possono eseguire altre istruzioni, quindi non vi è alcun vantaggio di parallelismo come affermato.
L'istruzione LEA può essere utilizzata per evitare calcoli che richiedono tempo per indirizzi efficaci da parte della CPU. Se un indirizzo viene utilizzato ripetutamente, è più efficace memorizzarlo in un registro invece di calcolare l'indirizzo effettivo ogni volta che viene utilizzato.
[esi]
raramente è più economico di quanto si pensi [esi + 4200]
ed è solo raramente più economico di [esi + ecx*8 + 4200]
.
[esi]
non è più economico di [esi + ecx*8 + 4200]
. Ma perché preoccuparsi del confronto? Non sono equivalenti. Se vuoi che il primo designi la stessa posizione di memoria del secondo, hai bisogno di ulteriori istruzioni: devi aggiungere esi
il valore di ecx
moltiplicato per 8. Uh oh, la moltiplicazione sta per bloccare le bandiere della tua CPU! Quindi devi aggiungere il 4200. Queste istruzioni aggiuntive aggiungono alla dimensione del codice (occupando spazio nella cache delle istruzioni, cicli per il recupero).
[esi + 4200]
ripetutamente in una sequenza di istruzioni, allora è meglio prima caricare l'indirizzo effettivo in un registro e usarlo. Ad esempio, piuttosto che scrivere add eax, [esi + 4200]; add ebx, [esi + 4200]; add ecx, [esi + 4200]
, dovresti preferire lea edi, [esi + 4200]; add eax, [edi]; add ebx, [edi]; add ecx, [edi]
, che raramente è più veloce. Almeno questa è la semplice interpretazione di questa risposta.
[esi]
e [esi + 4200]
(o [esi + ecx*8 + 4200]
è che questa è la semplificazione che l'OP sta proponendo (come lo capisco): che N istruzioni con lo stesso indirizzo complesso vengono trasformate in N istruzioni con indirizzamento semplice (un registro), più uno lea
, poiché l'indirizzamento complesso è "dispendioso in termini di tempo". In effetti, è più lento anche sul moderno x86, ma solo in termini di latenza che sembra improbabile per istruzioni consecutive con lo stesso indirizzo.
lea
quindi aumentare la pressione in quel caso. In generale, la memorizzazione di intermedi è una causa della pressione del registro, non una soluzione ad esso - ma penso che nella maggior parte dei casi si tratti di un lavaggio. @Kaz
L'istruzione LEA (Load Effective Address) è un modo per ottenere l'indirizzo che deriva da una qualsiasi delle modalità di indirizzamento della memoria del processore Intel.
Vale a dire, se abbiamo uno spostamento dei dati in questo modo:
MOV EAX, <MEM-OPERAND>
sposta il contenuto della posizione di memoria designata nel registro di destinazione.
Se sostituiamo il simbolo MOV
by LEA
, l'indirizzo della posizione di memoria viene calcolato esattamente allo stesso modo dall'espressione di <MEM-OPERAND>
indirizzamento. Ma invece del contenuto della posizione della memoria, otteniamo la posizione stessa nella destinazione.
LEA
non è un'istruzione aritmetica specifica; è un modo per intercettare l'indirizzo effettivo derivante da una qualsiasi delle modalità di indirizzamento della memoria del processore.
Ad esempio, possiamo usare LEA
solo un semplice indirizzo diretto. Nessuna aritmetica è coinvolta affatto:
MOV EAX, GLOBALVAR ; fetch the value of GLOBALVAR into EAX
LEA EAX, GLOBALVAR ; fetch the address of GLOBALVAR into EAX.
Questo è valido; possiamo provarlo al prompt di 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
Qui, non c'è aggiunta di un valore in scala e nessun offset. Zero viene spostato in EAX. Potremmo farlo usando anche MOV con un operando immediato.
Questo è il motivo per cui le persone che pensano che le parentesi LEA
siano superflue si sbagliano gravemente; le parentesi non sono LEA
sintassi ma fanno parte della modalità di indirizzamento.
LEA è reale a livello hardware. L'istruzione generata codifica la modalità di indirizzamento effettiva e il processore la esegue fino al punto di calcolo dell'indirizzo. Quindi sposta l'indirizzo verso la destinazione invece di generare un riferimento di memoria. (Poiché il calcolo dell'indirizzo di una modalità di indirizzamento in qualsiasi altra istruzione non ha alcun effetto sui flag della CPU, LEA
non ha alcun effetto sui flag della CPU.)
Contrasto con il caricamento del valore dall'indirizzo zero:
$ as
movl 0, %eax
$ objdump -d a.out | grep mov
0: 8b 04 25 00 00 00 00 mov 0x0,%eax
È una codifica molto simile, vedi? Solo il 8d
of LEA
è cambiato in 8b
.
Naturalmente, questa LEA
codifica è più lunga rispetto allo spostamento di uno zero immediato in EAX
:
$ as
movl $0, %eax
$ objdump -d a.out | grep mov
0: b8 00 00 00 00 mov $0x0,%eax
Non vi è alcun motivo per LEA
escludere questa possibilità solo perché esiste un'alternativa più breve; si sta semplicemente combinando in modo ortogonale con le modalità di indirizzamento disponibili.
Ecco un esempio
// 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;
}
Con -O (ottimizza) come opzione del compilatore, gcc troverà l'istruzione lea per la riga di codice indicata.
Sembra che molte risposte siano già complete, vorrei aggiungere un altro codice di esempio per mostrare come l'istruzione lea e move funzioni in modo diverso quando hanno lo stesso formato di espressione.
Per farla breve, sia le istruzioni lea che le istruzioni mov possono essere usate con le parentesi che racchiudono l'operando src delle istruzioni. Quando sono racchiusi tra () , l'espressione in () viene calcolata allo stesso modo; tuttavia, due istruzioni interpreteranno il valore calcolato nell'operando src in modo diverso.
Se l'espressione viene utilizzata con lea o mov, il valore src viene calcolato come di seguito.
D (Rb, Ri, S) => (Reg [Rb] + S * Reg [Ri] + D)
Tuttavia, quando viene utilizzato con l'istruzione mov, tenta di accedere al valore indicato dall'indirizzo generato dall'espressione precedente e di memorizzarlo nella destinazione.
Al contrario, quando l'istruzione lea viene eseguita con l'espressione precedente, carica il valore generato così com'è alla destinazione.
Il codice seguente esegue l'istruzione lea e l'istruzione mov con lo stesso parametro. Tuttavia, per cogliere la differenza, ho aggiunto un gestore di segnale a livello di utente per rilevare l'errore di segmentazione causato dall'accesso a un indirizzo errato a seguito dell'istruzione mov.
Codice di esempio
#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
);
}
Risultato dell'esecuzione
Result of lea instruction: 14
inst at 0x4007b5 tries to access memory at 14, but failed
=d
per dire al compilatore che il risultato è in EDX, salvando a mov
. Hai anche lasciato fuori una dichiarazione di clobber sull'output. Questo dimostra ciò che stai cercando di dimostrare, ma è anche un cattivo esempio fuorviante di asm inline che si romperà se utilizzato in altri contesti. Questa è una brutta cosa per una risposta di overflow dello stack.
%%
su tutti quei nomi di registro in Extended asm, utilizzare i vincoli di input. come asm("lea 4(%%ebx, %%eax, 8), %%edx" : "=d"(result_of_lea) : "a"(1), "b"(2));
. Consentire ai registri di inizializzazione del compilatore significa che non è necessario dichiarare i clobber. Stai complicando le cose con lo zero o l'azzeramento prima che mov-immediate sovrascriva anche l'intero registro.
mov 4(%ebx, %eax, 8), %edx
non è valido? Comunque, sì, mov
perché avrebbe senso scrivere "a"(1ULL)
per dire al compilatore che hai un valore a 64 bit, e quindi deve assicurarsi che sia esteso per riempire l'intero registro. In pratica lo userà ancora mov $1, %eax
, perché scrivere EAX zero-si estende in RAX, a meno che tu non abbia una strana situazione di codice circostante in cui il compilatore sapeva che RAX = 0xff00000001
o qualcosa del genere. Per lea
, stai ancora usando una dimensione di operando a 32 bit, quindi i bit alti dispersi nei registri di input non hanno alcun effetto sul risultato a 32 bit.
LEA: solo un'istruzione "aritmetica".
MOV trasferisce i dati tra gli operandi ma lea sta solo calcolando
mov eax, offset GLOBALVAR
invece. È possibile utilizzare LEA, ma è leggermente più grande di code-dimensioni rispetto mov r32, imm32
e gira su un minor numero di porte, perché va ancora attraverso il processo di indirizzo-di calcolo . lea reg, symbol
è utile solo a 64 bit per un LEA relativo al RIP, quando sono necessari PIC e / o indirizzi al di fuori dei 32 bit bassi. Nel codice a 32 o 16 bit, non vi è alcun vantaggio. LEA è un'istruzione aritmetica che espone la capacità della CPU di decodificare / calcolare le modalità di indirizzamento.
imul eax, edx, 1
non calcola: copia solo edx in eax. Ma in realtà esegue i tuoi dati attraverso il moltiplicatore con latenza di 3 cicli. O che rorx eax, edx, 0
semplicemente copia (ruota di zero).
Tutte le normali istruzioni di "calcolo" come l'aggiunta della moltiplicazione, l'esclusione o l'impostazione dei flag di stato come zero, firmano. Se si utilizza un indirizzo complicato, AX xor:= mem[0x333 +BX + 8*CX]
i flag vengono impostati in base all'operazione xor.
Ora potresti voler utilizzare l'indirizzo più volte. Il caricamento di tali indirizzi in un registro non ha mai lo scopo di impostare flag di stato e per fortuna no. La frase "carica indirizzo effettivo" lo rende consapevole del programmatore. Ecco da dove viene la strana espressione.
È chiaro che una volta che il processore è in grado di utilizzare l'indirizzo complicato per elaborare il suo contenuto, è in grado di calcolarlo per altri scopi. In effetti può essere usato per eseguire una trasformazione x <- 3*x+1
in un'istruzione. Questa è una regola generale nella programmazione degli assiemi: utilizzare le istruzioni comunque per far oscillare la barca.
L'unica cosa che conta è se la particolare trasformazione incarnata dall'istruzione è utile per te.
Linea di fondo
MOV, X| T| AX'| R| BX|
e
LEA, AX'| [BX]
hanno lo stesso effetto su AX ma non sui flag di stato. (Questa è la notazione di ciasdis .)
call lbl
lbl: pop rax
"lavorare" tecnicamente come un modo per ottenere il valore di rip
, ma renderai la previsione del ramo molto infelice. Usa le istruzioni come vuoi, ma non stupirti se fai qualcosa di complicato e ha conseguenze che non avevi previsto
Scusami se qualcuno lo ha già menzionato, ma nei giorni di x86 in cui la segmentazione della memoria era ancora rilevante, potresti non ottenere gli stessi risultati da queste due istruzioni:
LEA AX, DS:[0x1234]
e
LEA AX, CS:[0x1234]
seg:off
coppia. LEA non è influenzato dalla base del segmento; entrambe queste istruzioni verranno (in modo inefficiente) inserite 0x1234
in AX. x86 purtroppo non ha un modo semplice per calcolare un indirizzo lineare completo (efficace + base di segmenti) in un registro o in una coppia di registri.