Bagi saya, sepertinya MOV yang funky. Apa tujuannya dan kapan saya harus menggunakannya?
Bagi saya, sepertinya MOV yang funky. Apa tujuannya dan kapan saya harus menggunakannya?
Jawaban:
Seperti yang telah ditunjukkan orang lain, LEA (memuat alamat efektif) sering digunakan sebagai "trik" untuk melakukan perhitungan tertentu, tetapi itu bukan tujuan utamanya. Set instruksi x86 dirancang untuk mendukung bahasa tingkat tinggi seperti Pascal dan C, di mana array — terutama array int atau struct kecil — adalah umum. Pertimbangkan, misalnya, struct yang mewakili (x, y) koordinat:
struct Point
{
int xcoord;
int ycoord;
};
Sekarang bayangkan sebuah pernyataan seperti:
int y = points[i].ycoord;
dimana points[]
array dari Point
. Dengan asumsi basis array sudah dalam EBX
, dan variabel i
dalam EAX
, dan xcoord
dan ycoord
masing-masing 32 bit (demikian ycoord
juga pada offset 4 byte dalam struct), pernyataan ini dapat dikompilasi ke:
MOV EDX, [EBX + 8*EAX + 4] ; right side is "effective address"
yang akan mendarat y
di EDX
. Faktor skala 8 adalah karena masing Point
- masing berukuran 8 byte. Sekarang perhatikan ungkapan yang sama yang digunakan dengan operator "alamat" &:
int *p = &points[i].ycoord;
Dalam hal ini, Anda tidak ingin nilai ycoord
, tetapi alamatnya. Di situlah LEA
(memuat alamat efektif) masuk. Alih-alih MOV
, kompiler dapat menghasilkan
LEA ESI, [EBX + 8*EAX + 4]
yang akan memuat alamat di ESI
.
mov
instruksi dan meninggalkan tanda kurung? MOV EDX, EBX + 8*EAX + 4
MOV
dengan sumber tidak langsung, kecuali itu hanya tipuan dan bukan MOV
. Sebenarnya tidak membaca dari alamat yang dihitung, hanya menghitungnya.
Dari "Zen Majelis" oleh Abrash:
LEA
, satu-satunya instruksi yang melakukan perhitungan pengalamatan memori tetapi tidak benar-benar mengatasi memori.LEA
menerima operan pengalamatan memori standar, tetapi tidak lebih dari menyimpan offset memori yang dihitung dalam register yang ditentukan, yang mungkin merupakan register tujuan umum.Apa yang memberi kita? Dua hal yang
ADD
tidak memberikan:
- kemampuan untuk melakukan penambahan dengan dua atau tiga operan, dan
- kemampuan untuk menyimpan hasil dalam register apa pun ; bukan hanya salah satu dari operan sumber.
Dan LEA
tidak mengubah bendera.
Contohnya
LEA EAX, [ EAX + EBX + 1234567 ]
menghitung EAX + EBX + 1234567
(itulah tiga operan)LEA EAX, [ EBX + ECX ]
menghitung EBX + ECX
tanpa mengesampingkan baik dengan hasilnya.LEA EAX, [ EBX + N * EBX ]
(N bisa 1,2,4,8).Usecase lain berguna dalam loop: perbedaan antara LEA EAX, [ EAX + 1 ]
dan INC EAX
adalah bahwa yang terakhir berubah EFLAGS
tetapi yang pertama tidak; ini mempertahankan CMP
keadaan.
LEA EAX, [ EAX + EBX + 1234567 ]
menghitung jumlah EAX
, EBX
dan 1234567
(itulah tiga operan). LEA EAX, [ EBX + ECX ]
menghitung EBX + ECX
tanpa mengesampingkan baik dengan hasilnya. Hal ketiga yang LEA
digunakan untuk (tidak terdaftar oleh Frank) adalah perkalian dengan konstanta (oleh dua, tiga, lima atau sembilan), jika Anda menggunakannya seperti LEA EAX, [ EBX + N * EBX ]
( N
bisa 1,2,4,8). Usecase lain berguna dalam loop: perbedaan antara LEA EAX, [ EAX + 1 ]
dan INC EAX
adalah bahwa yang terakhir berubah EFLAGS
tetapi yang pertama tidak; ini mempertahankan CMP
negara
LEA
dapat digunakan untuk ... (lihat "LEA (memuat alamat efektif) sering digunakan sebagai" trik "untuk melakukan perhitungan tertentu" dalam jawaban populer IJ Kennedy di atas)
Fitur penting lainnya dari LEA
instruksi adalah bahwa ia tidak mengubah kode kondisi seperti CF
dan ZF
, saat menghitung alamat dengan instruksi aritmatika suka ADD
atau MUL
tidak. Fitur ini mengurangi tingkat ketergantungan di antara instruksi dan dengan demikian membuat ruang untuk optimasi lebih lanjut oleh kompiler atau penjadwal perangkat keras.
lea
kadang berguna bagi kompiler (atau pengode manusia) untuk melakukan matematika tanpa mengalahkan hasil flag. Tetapi lea
tidak lebih cepat dari itu add
. Sebagian besar instruksi x86 menulis flag. Implementasi x86 berkinerja tinggi harus mengubah nama EFLAGS atau menghindari bahaya write-after-write agar kode normal berjalan cepat, jadi instruksi yang menghindari flag write tidak lebih baik karena itu. ( hal-hal bendera parsial dapat menimbulkan masalah, lihat instruksi INC vs. ADD 1: Apakah penting? )
Terlepas dari semua penjelasan, LEA adalah operasi aritmatika:
LEA Rt, [Rs1+a*Rs2+b] => Rt = Rs1 + a*Rs2 + b
Hanya saja namanya sangat bodoh untuk operasi shift + add. Alasan untuk itu sudah dijelaskan dalam jawaban berperingkat teratas (yaitu ia dirancang untuk secara langsung memetakan referensi memori tingkat tinggi).
LEA
pada AGU tetapi pada ALU integer biasa. Kita harus membaca spesifikasi CPU sangat dekat hari ini untuk mencari tahu "di mana hal-hal berjalan" ...
LEA
memberi Anda alamat yang muncul dari mode pengalamatan terkait memori. Ini bukan shift dan operasi tambahan.
Mungkin hanya hal lain tentang instruksi LEA. Anda juga dapat menggunakan LEA untuk register yang berlipat ganda dengan 3, 5 atau 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
instruksi shift kiri seperti untuk mengalikan register dengan 2,4,8,16 ... lebih cepat dan lebih pendek. Tetapi untuk mengalikan dengan angka yang berbeda dari kekuatan 2 kita biasanya menggunakan mul
instruksi yang lebih megah dan lebih lambat.
lea eax,[eax*3]
akan menerjemahkan ke setara dengan lea eax,[eax+eax*2]
.
lea
adalah singkatan dari "memuat alamat efektif". Itu memuat alamat referensi lokasi oleh operan sumber ke operan tujuan. Misalnya, Anda dapat menggunakannya untuk:
lea ebx, [ebx+eax*8]
untuk memindahkan item ebx
pointer eax
lebih lanjut (dalam array 64-bit / elemen) dengan satu instruksi. Pada dasarnya, Anda mendapat manfaat dari mode pengalamatan kompleks yang didukung oleh arsitektur x86 untuk memanipulasi pointer secara efisien.
Alasan terbesar yang Anda gunakan di LEA
atas MOV
adalah jika Anda perlu melakukan aritmatika pada register yang Anda gunakan untuk menghitung alamat. Secara efektif, Anda dapat melakukan jumlah aritmatika penunjuk pada beberapa register dalam kombinasi secara efektif untuk "gratis."
Apa yang benar-benar membingungkan adalah bahwa Anda biasanya menulis LEA
seperti MOV
tetapi Anda tidak benar-benar mengingat memori. Dengan kata lain:
MOV EAX, [ESP+4]
Ini akan memindahkan konten ESP+4
ke poin mana EAX
.
LEA EAX, [EBX*8]
Ini akan memindahkan alamat efektif EBX * 8
ke EAX, bukan yang ditemukan di lokasi itu. Seperti yang dapat Anda lihat, juga dimungkinkan untuk memperbanyak dengan dua faktor (penskalaan) sementara a MOV
terbatas pada penambahan / pengurangan.
LEA
dilakukan.
8086 memiliki keluarga besar instruksi yang menerima operan register dan alamat efektif, melakukan beberapa perhitungan untuk menghitung bagian offset dari alamat efektif itu, dan melakukan beberapa operasi yang melibatkan register dan memori yang dirujuk oleh alamat yang dihitung. Cukup sederhana untuk memiliki salah satu instruksi dalam keluarga itu berperilaku seperti di atas kecuali untuk melewatkan operasi memori yang sebenarnya. Ini, instruksi:
mov ax,[bx+si+5]
lea ax,[bx+si+5]
diimplementasikan hampir identik secara internal. Perbedaannya adalah langkah yang dilompati. Kedua instruksi tersebut bekerja seperti:
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
Mengenai mengapa Intel menganggap instruksi ini layak untuk dimasukkan, saya tidak begitu yakin, tetapi fakta bahwa itu murah untuk diterapkan akan menjadi faktor besar. Faktor lain adalah fakta bahwa assembler Intel memperbolehkan simbol untuk didefinisikan relatif terhadap register BP. Jika fnord
didefinisikan sebagai simbol relatif-BP (mis. BP + 8), bisa dikatakan:
mov ax,fnord ; Equivalent to "mov ax,[BP+8]"
Jika seseorang ingin menggunakan sesuatu seperti stosw untuk menyimpan data ke alamat relatif BP, bisa mengatakannya
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
lebih nyaman daripada:
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
Perhatikan bahwa melupakan dunia "offset" akan menyebabkan konten lokasi [BP + 8], bukannya nilai 8, ditambahkan ke DI. Ups.
Seperti jawaban yang ada disebutkan, LEA
memiliki keuntungan melakukan memori pengalamatan aritmatika tanpa mengakses memori, menyimpan hasil aritmatika ke register yang berbeda, bukan bentuk sederhana dari instruksi tambahan. Manfaat kinerja yang mendasari nyata adalah bahwa prosesor modern memiliki unit dan port LEA ALU terpisah untuk pembuatan alamat yang efektif (termasuk LEA
dan alamat referensi memori lainnya), ini berarti operasi aritmatika LEA
dan operasi aritmatika normal lainnya di ALU dapat dilakukan secara paralel dalam satu inti.
Lihat artikel arsitektur Haswell ini untuk beberapa detail tentang unit LEA: http://www.realworldtech.com/haswell-cpu/4/
Poin penting lain yang tidak disebutkan dalam jawaban lain adalah LEA REG, [MemoryAddress]
instruksi adalah PIC (position independent code) yang menyandikan alamat relatif PC dalam instruksi ini sebagai referensi MemoryAddress
. Ini berbeda dari MOV REG, MemoryAddress
yang mengkodekan alamat virtual relatif dan memerlukan relokasi / patching dalam sistem operasi modern (seperti ASLR adalah fitur umum). Jadi LEA
dapat digunakan untuk mengkonversi non PIC ke PIC.
lea
pada satu atau lebih ALU yang sama yang mengeksekusi instruksi aritmatika lainnya (tetapi umumnya lebih sedikit dari mereka daripada aritmatika lainnya). Misalnya, CPU Haswell yang disebutkan dapat menjalankan add
atau sub
atau sebagian besar operasi aritmatika dasar lainnya pada empat ALU yang berbeda , tetapi hanya dapat mengeksekusi lea
pada satu (kompleks lea
) atau dua (sederhana lea
). Lebih penting lagi, dua lea
ALU yang dapat dilewati hanyalah dua dari empat ALU yang dapat menjalankan instruksi lain, sehingga tidak ada manfaat paralelisme seperti yang diklaim.
Instruksi LEA dapat digunakan untuk menghindari perhitungan yang memakan waktu dari alamat yang efektif oleh CPU. Jika suatu alamat digunakan berulang kali, lebih efektif untuk menyimpannya dalam register daripada menghitung alamat efektif setiap kali digunakan.
[esi]
jarang lebih murah daripada mengatakan [esi + 4200]
dan hanya jarang lebih murah daripada [esi + ecx*8 + 4200]
.
[esi]
tidak lebih murah dari [esi + ecx*8 + 4200]
. Tapi mengapa repot-repot membandingkan? Mereka tidak setara. Jika Anda ingin yang pertama menunjuk lokasi memori yang sama dengan yang kedua, Anda memerlukan instruksi tambahan: Anda harus menambah esi
nilai ecx
dikalikan dengan 8. Eh, multiplikasi akan merusak flag CPU Anda! Kemudian Anda harus menambahkan 4200. Instruksi tambahan ini menambah ukuran kode (mengambil ruang dalam cache instruksi, siklus untuk mengambil).
[esi + 4200]
berulang kali dalam urutan instruksi, maka lebih baik untuk memuat alamat efektif ke dalam register dan menggunakannya. Misalnya, daripada menulis add eax, [esi + 4200]; add ebx, [esi + 4200]; add ecx, [esi + 4200]
, Anda harus memilih lea edi, [esi + 4200]; add eax, [edi]; add ebx, [edi]; add ecx, [edi]
, yang jarang lebih cepat. Setidaknya itulah interpretasi sederhana dari jawaban ini.
[esi]
dan [esi + 4200]
(atau [esi + ecx*8 + 4200]
apakah ini adalah penyederhanaan yang diusulkan OP (seperti yang saya pahami): bahwa instruksi N dengan alamat kompleks yang sama ditransformasikan menjadi instruksi N dengan pengalamatan sederhana (satu reg), ditambah satu lea
, karena pengalamatan yang kompleks adalah "memakan waktu". Faktanya, pengalamatan lebih lambat bahkan pada x86 modern, tetapi hanya dari latensi yang tampaknya tidak penting untuk instruksi berurutan dengan alamat yang sama.
lea
sehingga meningkatkan tekanan dalam kasus itu. Secara umum, menyimpan perantara adalah penyebab tekanan register, bukan solusi untuk itu - tapi saya pikir dalam sebagian besar situasi itu adalah mencuci. @Kaz
Instruksi LEA (Load Effective Address) adalah cara untuk mendapatkan alamat yang muncul dari mode pengalamatan memori prosesor Intel.
Artinya, jika kita memiliki data yang bergerak seperti ini:
MOV EAX, <MEM-OPERAND>
itu memindahkan isi dari lokasi memori yang ditunjuk ke register target.
Jika kita mengganti MOV
dengan LEA
, maka alamat lokasi memori dihitung dengan cara yang persis sama dengan <MEM-OPERAND>
ekspresi pengalamatan. Namun alih-alih isi lokasi memori, kami mendapatkan lokasi itu sendiri ke tujuan.
LEA
bukan instruksi aritmatika tertentu; ini adalah cara mencegat alamat efektif yang timbul dari salah satu mode pengalamatan memori prosesor.
Misalnya, kita dapat menggunakan LEA
hanya pada alamat langsung yang sederhana. Tidak ada aritmatika yang terlibat sama sekali:
MOV EAX, GLOBALVAR ; fetch the value of GLOBALVAR into EAX
LEA EAX, GLOBALVAR ; fetch the address of GLOBALVAR into EAX.
Ini valid; kita dapat mengujinya di Linux prompt:
$ 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
Di sini, tidak ada penambahan nilai skala, dan tidak ada offset. Nol dipindahkan ke EAX. Kita bisa melakukannya menggunakan MOV dengan operan langsung juga.
Ini adalah alasan mengapa orang-orang yang berpikir bahwa tanda kurung LEA
terlalu berlebihan salah besar; tanda kurung bukan LEA
sintaks tetapi merupakan bagian dari mode pengalamatan.
LEA nyata di tingkat perangkat keras. Instruksi yang dihasilkan mengkodekan mode pengalamatan aktual dan prosesor membawanya ke titik penghitungan alamat. Kemudian memindahkan alamat itu ke tujuan alih-alih menghasilkan referensi memori. (Karena perhitungan alamat dari mode pengalamatan dalam instruksi lain tidak berpengaruh pada flag CPU, LEA
tidak berpengaruh pada flag CPU.)
Berbeda dengan memuat nilai dari alamat nol:
$ as
movl 0, %eax
$ objdump -d a.out | grep mov
0: 8b 04 25 00 00 00 00 mov 0x0,%eax
Ini encoding yang sangat mirip, lihat? Hanya 8d
dari LEA
telah berubah menjadi 8b
.
Tentu saja, LEA
pengkodean ini lebih lama daripada memindahkan nol langsung ke EAX
:
$ as
movl $0, %eax
$ objdump -d a.out | grep mov
0: b8 00 00 00 00 mov $0x0,%eax
Tidak ada alasan untuk LEA
mengecualikan kemungkinan ini hanya karena ada alternatif yang lebih pendek; itu hanya menggabungkan secara ortogonal dengan mode pengalamatan yang tersedia.
Berikut ini sebuah contoh.
// 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;
}
Dengan -O (optimisasi) sebagai opsi kompiler, gcc akan menemukan instruksi lea untuk baris kode yang ditunjukkan.
Tampaknya banyak jawaban sudah selesai, saya ingin menambahkan satu lagi contoh kode untuk menunjukkan bagaimana lea dan memindahkan instruksi bekerja secara berbeda ketika mereka memiliki format ekspresi yang sama.
Untuk membuat cerita panjang pendek, instruksi lea dan instruksi mov keduanya dapat digunakan dengan tanda kurung melampirkan operan src dari instruksi. Ketika mereka diapit dengan () , ekspresi dalam () dihitung dengan cara yang sama; namun, dua instruksi akan menginterpretasikan nilai yang dihitung dalam operan src dengan cara yang berbeda.
Apakah ekspresi digunakan dengan lea atau mov, nilai src dihitung seperti di bawah ini.
D (Rb, Ri, S) => (Reg [Rb] + S * Reg [Ri] + D)
Namun, ketika digunakan dengan instruksi mov, ia mencoba mengakses nilai yang ditunjukkan oleh alamat yang dihasilkan oleh ekspresi di atas dan menyimpannya ke tujuan.
Sebaliknya, ketika instruksi lea dieksekusi dengan ekspresi di atas, ia memuat nilai yang dihasilkan sebagaimana ke tujuan.
Kode di bawah ini mengeksekusi instruksi lea dan instruksi mov dengan parameter yang sama. Namun, untuk menangkap perbedaannya, saya menambahkan penangan sinyal level pengguna untuk menangkap kesalahan segmentasi yang disebabkan oleh mengakses alamat yang salah sebagai hasil dari instruksi mov.
Kode contoh
#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
);
}
Hasil eksekusi
Result of lea instruction: 14
inst at 0x4007b5 tries to access memory at 14, but failed
=d
untuk memberi tahu kompiler bahwa hasilnya ada di EDX, menyimpan a mov
. Anda juga meninggalkan deklarasi clobber awal pada output. Ini menunjukkan apa yang Anda coba tunjukkan, tetapi juga merupakan contoh buruk inline asm yang menyesatkan yang akan pecah jika digunakan dalam konteks lain. Itu Hal Buruk untuk jawaban stack overflow.
%%
semua nama daftar itu dalam Extended asm, maka gunakan batasan input. seperti asm("lea 4(%%ebx, %%eax, 8), %%edx" : "=d"(result_of_lea) : "a"(1), "b"(2));
. Membiarkan register init compiler berarti Anda juga tidak harus mendeklarasikan clobbers. Anda terlalu rumit dengan xor-zeroing sebelum mov-direct menimpa seluruh register juga.
mov 4(%ebx, %eax, 8), %edx
tidak valid? Lagi pula, ya, karena mov
akan masuk akal untuk menulis "a"(1ULL)
untuk memberi tahu kompiler Anda memiliki nilai 64-bit, dan dengan demikian perlu memastikan itu diperpanjang untuk mengisi seluruh register. Dalam praktiknya masih akan digunakan mov $1, %eax
, karena menulis EAX nol meluas ke RAX, kecuali jika Anda memiliki situasi aneh kode sekitarnya di mana kompiler tahu bahwa RAX = 0xff00000001
atau sesuatu. Sebab lea
, Anda masih menggunakan ukuran operan 32-bit, sehingga bit tinggi yang tersesat di register input tidak berpengaruh pada hasil 32-bit.
LEA: hanya instruksi "aritmatika" ..
MOV mentransfer data antar operan tetapi lea hanya menghitung
mov eax, offset GLOBALVAR
saja. Anda dapat menggunakan LEA, tetapi ukuran kode sedikit lebih besar daripada mov r32, imm32
dan berjalan pada lebih sedikit port, karena masih melewati proses perhitungan alamat . lea reg, symbol
hanya berguna dalam 64-bit untuk LEA RIP-relatif, ketika Anda membutuhkan PIC dan / atau alamat di luar 32 bit yang rendah. Dalam kode 32 atau 16-bit, tidak ada keuntungan. LEA adalah instruksi aritmatika yang memperlihatkan kemampuan CPU untuk mendekode / menghitung mode pengalamatan.
imul eax, edx, 1
tidak menghitung: itu hanya menyalin edx ke eax. Tapi sebenarnya itu menjalankan data Anda melalui pengganda dengan latensi 3 siklus. Atau itu rorx eax, edx, 0
hanya salinan (rotasikan oleh nol).
Semua instruksi "kalkulasi" normal seperti menambahkan perkalian, eksklusif atau mengatur tanda status seperti nol, tandatangani. Jika Anda menggunakan alamat yang rumit, AX xor:= mem[0x333 +BX + 8*CX]
bendera ditetapkan sesuai dengan operasi xor.
Sekarang Anda mungkin ingin menggunakan alamat tersebut beberapa kali. Memuat addres semacam itu ke dalam register tidak pernah dimaksudkan untuk menetapkan bendera status dan untungnya tidak. Ungkapan "memuat alamat yang efektif" membuat programmer mengetahui hal itu. Dari situlah ekspresi aneh itu berasal.
Jelas bahwa begitu prosesor mampu menggunakan alamat yang rumit untuk memproses kontennya, ia mampu menghitungnya untuk keperluan lain. Memang itu dapat digunakan untuk melakukan transformasi x <- 3*x+1
dalam satu instruksi. Ini adalah aturan umum dalam pemrograman rakitan: Gunakan instruksi namun itu menggetarkan perahu Anda.
Satu-satunya hal yang diperhitungkan adalah apakah transformasi yang diwujudkan oleh instruksi bermanfaat bagi Anda.
Intinya
MOV, X| T| AX'| R| BX|
dan
LEA, AX'| [BX]
memiliki efek yang sama pada AX tetapi tidak pada bendera status. (Ini adalah notasi ciasdis .)
call lbl
lbl: pop rax
"bekerja" secara teknis sebagai cara untuk mendapatkan nilai rip
, tetapi Anda akan membuat prediksi cabang sangat tidak bahagia. Gunakan instruksi yang Anda inginkan, tetapi jangan kaget jika Anda melakukan sesuatu yang rumit dan memiliki konsekuensi yang tidak Anda perkirakan
Maafkan saya jika seseorang telah menyebutkannya, tetapi di masa x86 ketika segmentasi memori masih relevan, Anda mungkin tidak mendapatkan hasil yang sama dari dua instruksi ini:
LEA AX, DS:[0x1234]
dan
LEA AX, CS:[0x1234]
seg:off
pasangan. LEA tidak terpengaruh oleh basis segmen; kedua instruksi tersebut akan (secara tidak efisien) dimasukkan 0x1234
ke dalam AX. x86 sayangnya tidak memiliki cara mudah untuk menghitung alamat linear lengkap (basis + segmen efektif) menjadi register atau pasangan-pasangan.