Lihat juga Menggunakan LEA pada nilai yang bukan alamat / petunjuk? : LEA hanyalah instruksi shift-and-add. Itu mungkin ditambahkan ke 8086 karena perangkat keras sudah ada di sana untuk memecahkan kode dan menghitung mode pengalamatan, bukan karena itu "dimaksudkan" hanya untuk digunakan dengan alamat. Ingat bahwa pointer hanya bilangan bulat dalam perakitan.
Peter Cordes
Jawaban:
798
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 idalam EAX, dan xcoorddan ycoordmasing-masing 32 bit (demikian ycoordjuga 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 ydi 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
Bukankah lebih bersih untuk memperpanjang movinstruksi dan meninggalkan tanda kurung? MOV EDX, EBX + 8*EAX + 4
Natan Yellin
14
@imacake Dengan mengganti LEA dengan MOV khusus, Anda menjaga sintaks tetap bersih: [] kurung selalu sama dengan mendereferensi pointer di C. Tanpa kurung, Anda selalu berurusan dengan pointer itu sendiri.
Natan Yellin
139
Melakukan matematika dalam instruksi MOV (EBX + 8 * EAX + 4) tidak valid. LEA ESI, [EBX + 8 * EAX + 4] valid karena ini adalah mode pengalamatan yang didukung x86. en.wikipedia.org/wiki/X86#Addressing_modes
Erik
29
@JonathanDickinson LEA seperti MOVdengan sumber tidak langsung, kecuali itu hanya tipuan dan bukan MOV. Sebenarnya tidak membaca dari alamat yang dihitung, hanya menghitungnya.
hobbs
24
Erik, komentar tur tidak akurat. MOV eax, [ebx + 8 * ecx + 4] valid. Namun MOV mengembalikan konten lokasi memori sedangkan LEA mengembalikan alamat
Olorin
562
Dari "Zen Majelis" oleh Abrash:
LEA, satu-satunya instruksi yang melakukan perhitungan pengalamatan memori tetapi tidak benar-benar mengatasi memori. LEAmenerima 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 ADDtidak 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.
LEA EAX, [ EBX + ECX ]menghitung EBX + ECXtanpa mengesampingkan baik dengan hasilnya.
perkalian dengan konstan (dengan dua, tiga, lima atau sembilan), jika Anda suka LEA EAX, [ EBX + N * EBX ](N bisa 1,2,4,8).
Usecase lain berguna dalam loop: perbedaan antara LEA EAX, [ EAX + 1 ]dan INC EAXadalah bahwa yang terakhir berubah EFLAGStetapi yang pertama tidak; ini mempertahankan CMPkeadaan.
@AbidRahmanK beberapa contoh: LEA EAX, [ EAX + EBX + 1234567 ]menghitung jumlah EAX, EBXdan 1234567(itulah tiga operan). LEA EAX, [ EBX + ECX ]menghitung EBX + ECXtanpa mengesampingkan baik dengan hasilnya. Hal ketiga yang LEAdigunakan untuk (tidak terdaftar oleh Frank) adalah perkalian dengan konstanta (oleh dua, tiga, lima atau sembilan), jika Anda menggunakannya seperti LEA EAX, [ EBX + N * EBX ]( Nbisa 1,2,4,8). Usecase lain berguna dalam loop: perbedaan antara LEA EAX, [ EAX + 1 ]dan INC EAXadalah bahwa yang terakhir berubah EFLAGStetapi yang pertama tidak; ini mempertahankan CMPnegara
FrankH.
@ Frankh. Saya masih tidak mengerti, jadi memuat pointer ke tempat lain?
6
@ ripDaddy69 ya, semacam - jika dengan "memuat" maksud Anda "melakukan perhitungan alamat / pointer arithmetics". Itu tidak mengakses memori (yaitu bukan "dereference" pointer seperti yang akan disebut dalam istilah pemrograman C).
FrankH.
2
+1: Ini menjelaskan 'trik' apa yang LEAdapat digunakan untuk ... (lihat "LEA (memuat alamat efektif) sering digunakan sebagai" trik "untuk melakukan perhitungan tertentu" dalam jawaban populer IJ Kennedy di atas)
Assad Ebrahim
3
Ada perbedaan besar antara 2 operan LEA yang cepat dan 3 operan LEA yang lambat. Manual Pengoptimalan Intel mengatakan LEA jalur cepat adalah siklus tunggal dan jalur lambat LEA membutuhkan tiga siklus. Selain itu, pada Skylake ada dua unit fungsional jalur cepat (port 1 dan 5) dan hanya ada satu unit fungsional jalur lambat (port 1). Assembly / Compiler Coding Rule 33 dalam manual bahkan memperingatkan untuk tidak menggunakan 3 operan LEA.
Olsonist
110
Fitur penting lainnya dari LEAinstruksi adalah bahwa ia tidak mengubah kode kondisi seperti CFdan ZF, saat menghitung alamat dengan instruksi aritmatika suka ADDatau MULtidak. Fitur ini mengurangi tingkat ketergantungan di antara instruksi dan dengan demikian membuat ruang untuk optimasi lebih lanjut oleh kompiler atau penjadwal perangkat keras.
Ya, leakadang berguna bagi kompiler (atau pengode manusia) untuk melakukan matematika tanpa mengalahkan hasil flag. Tetapi leatidak 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? )
Peter Cordes
2
@PeterCordes: Benci untuk membawa ini ke sini tapi - apakah saya sendirian dalam memikirkan tag [x86-lea] baru ini berlebihan dan tidak perlu?
Michael Petch
2
@MichaelPetch: Ya, saya pikir itu terlalu spesifik. Tampaknya membingungkan pemula yang tidak mengerti bahasa mesin dan bahwa segala sesuatu (termasuk pointer) hanya bit / byte / bilangan bulat, jadi ada banyak pertanyaan tentang hal itu dengan jumlah suara yang besar. Tetapi memiliki tag untuk itu menyiratkan bahwa ada ruang untuk sejumlah pertanyaan terbuka di masa depan, padahal sebenarnya ada sekitar 2 atau 3 total yang tidak hanya duplikat. (apa itu? Bagaimana menggunakannya untuk mengalikan bilangan bulat? dan bagaimana itu berjalan secara internal pada AGU vs ALU dan dengan apa latensi / throughput. Dan mungkin itu tujuan "dimaksudkan")
Peter Cordes
@PeterCordes: Saya setuju, dan jika ada semua postingan yang sedang diedit ini adalah duplikat dari beberapa pertanyaan terkait LEA yang ada. Daripada tag, duplikat apa pun harus diidentifikasi dan ditandai sebagai imho.
Michael Petch
1
@EvanCarroll: tunggu menandai semua pertanyaan LEA, jika Anda belum selesai. Seperti dibahas di atas, kami pikir x86-lea terlalu spesifik untuk sebuah tag, dan tidak ada banyak ruang untuk pertanyaan non-duplikat di masa depan. Saya pikir itu akan menjadi banyak pekerjaan untuk benar - benar memilih T&J "terbaik" sebagai target dup bagi kebanyakan dari mereka, atau untuk benar-benar memutuskan mana yang akan mendapatkan mod untuk digabung.
Peter Cordes
93
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).
Dan bahwa aritmatika dilakukan oleh perangkat keras penghitungan alamat.
Ben Voigt
30
@ BenVoigt, saya biasa mengatakan itu, karena saya orang tua :-) Secara tradisional, CPU x86 memang menggunakan unit pengalamatan untuk ini, setuju. Tetapi "pemisahan" menjadi sangat buram akhir-akhir ini. Beberapa CPU tidak lagi memiliki AGU khusus , yang lain memilih untuk tidak mengeksekusi LEApada AGU tetapi pada ALU integer biasa. Kita harus membaca spesifikasi CPU sangat dekat hari ini untuk mencari tahu "di mana hal-hal berjalan" ...
FrankH.
2
@ Frankh .: CPU rusak biasanya menjalankan LEA pada ALU, sementara beberapa CPU tidak teratur (seperti Atom) terkadang menjalankannya pada AGU (karena mereka tidak bisa sibuk menangani akses memori).
Peter Cordes
3
Tidak, namanya tidak bodoh. LEAmemberi Anda alamat yang muncul dari mode pengalamatan terkait memori. Ini bukan shift dan operasi tambahan.
Kaz
3
FWIW ada sangat sedikit (jika ada) CPU x86 saat ini yang melakukan operasi pada AGU. Sebagian besar atau semua hanya menggunakan ALU seperti op aritmatika lainnya.
BeeOnRope
77
Mungkin hanya hal lain tentang instruksi LEA. Anda juga dapat menggunakan LEA untuk register yang berlipat ganda dengan 3, 5 atau 9.
+1 untuk triknya. Tapi saya ingin mengajukan pertanyaan (mungkin bodoh), mengapa tidak langsung memperbanyak dengan tiga seperti ini LEA EAX, [EAX*3]?
Abid Rahman K
13
@Abid Rahman K: Tidak ada instruksi seperti itu dan set instruksi CPU x86.
GJ.
50
@AbidRahmanK meskipun sintaks intel asm membuatnya terlihat seperti multiplikasi, instruksi lea hanya dapat menyandikan operasi shift. Opcode memiliki 2 bit untuk menggambarkan pergeseran, maka Anda dapat mengalikan hanya dengan 1,2,4 atau 8.
ithkuil
6
@ Koray Tugay: Anda dapat menggunakan shlinstruksi 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 mulinstruksi yang lebih megah dan lebih lambat.
GJ.
7
@ GJ. walaupun tidak ada pengkodean seperti itu, beberapa assembler menerima ini sebagai jalan pintas, misalnya fasm. Jadi misalnya lea eax,[eax*3]akan menerjemahkan ke setara dengan lea eax,[eax+eax*2].
Ruslan
59
leaadalah 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 ebxpointer eaxlebih 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 LEAatas MOVadalah 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 LEAseperti MOVtetapi Anda tidak benar-benar mengingat memori. Dengan kata lain:
MOV EAX, [ESP+4]
Ini akan memindahkan konten ESP+4ke poin mana EAX.
LEA EAX, [EBX*8]
Ini akan memindahkan alamat efektif EBX * 8ke EAX, bukan yang ditemukan di lokasi itu. Seperti yang dapat Anda lihat, juga dimungkinkan untuk memperbanyak dengan dua faktor (penskalaan) sementara a MOVterbatas pada penambahan / pengurangan.
Maaf semuanya @ big.heart membodohi saya dengan memberikan jawaban untuk ini tiga jam yang lalu, membuatnya muncul sebagai "baru" dalam menjelajahi pertanyaan Majelis saya.
David Hoelzer
1
Mengapa sintaksis menggunakan tanda kurung ketika tidak melakukan pengalamatan memori?
golopot
3
@ q4w56 Ini adalah salah satu hal di mana jawabannya adalah, "Begitulah cara Anda melakukannya." Saya percaya ini adalah salah satu alasan orang sulit menemukan apa yang harus LEAdilakukan.
David Hoelzer
2
@ q4w56: ini adalah shift + add instruksi yang menggunakan sintaks operand memori dan pengkodean kode mesin. Pada beberapa CPU bahkan mungkin menggunakan perangkat keras AGU, tapi itu adalah detail historis. Fakta yang masih relevan adalah bahwa perangkat keras decoder sudah ada untuk mendekode jenis shift + add ini, dan LEA memungkinkan kita menggunakannya untuk aritmatika alih-alih pengalamatan memori. (Atau untuk perhitungan alamat jika satu input sebenarnya adalah pointer).
Peter Cordes
20
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 fnorddidefinisikan 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, LEAmemiliki 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 LEAdan alamat referensi memori lainnya), ini berarti operasi aritmatika LEAdan operasi aritmatika normal lainnya di ALU dapat dilakukan secara paralel dalam satu inti.
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, MemoryAddressyang mengkodekan alamat virtual relatif dan memerlukan relokasi / patching dalam sistem operasi modern (seperti ASLR adalah fitur umum). Jadi LEAdapat digunakan untuk mengkonversi non PIC ke PIC.
Bagian "LEA ALU" terpisah sebagian besar tidak benar. CPU modern mengeksekusi leapada 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 addatau subatau sebagian besar operasi aritmatika dasar lainnya pada empat ALU yang berbeda , tetapi hanya dapat mengeksekusi leapada satu (kompleks lea) atau dua (sederhana lea). Lebih penting lagi, dua leaALU yang dapat dilewati hanyalah dua dari empat ALU yang dapat menjalankan instruksi lain, sehingga tidak ada manfaat paralelisme seperti yang diklaim.
BeeOnRope
Artikel yang Anda tautkan (dengan benar) menunjukkan bahwa LEA berada di port yang sama dengan ALU integer (add / sub / boolean), dan unit MUL integer di Haswell. (Dan vektor ALU termasuk FP ADD / MUL / FMA). Unit LEA hanya-sederhana adalah pada port 5, yang juga menjalankan ADD / SUB / apa pun, dan vektor shuffles, dan hal-hal lainnya. Satu-satunya alasan saya tidak downvoting adalah bahwa Anda menunjukkan penggunaan LEA RIP-relatif (untuk x86-64 saja).
Peter Cordes
8
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.
Tidak harus pada x86 modern. Sebagian besar mode pengalamatan memiliki biaya yang sama, dengan beberapa peringatan. Jadi [esi]jarang lebih murah daripada mengatakan [esi + 4200]dan hanya jarang lebih murah daripada [esi + ecx*8 + 4200].
BeeOnRope
@BeeOnRope [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 esinilai ecxdikalikan 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).
Kaz
2
@ Ka - Saya pikir Anda kehilangan poin saya (atau saya melewatkan titik OP). Pemahaman saya adalah bahwa OP mengatakan bahwa jika Anda akan menggunakan sesuatu seperti [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.
BeeOnRope
Jadi alasan saya membandingkan [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.
BeeOnRope
1
Mungkin Anda mengurangi tekanan register, ya - tetapi yang sebaliknya mungkin terjadi: jika register yang Anda buat alamat efektifnya aktif, Anda perlu register lain untuk menyimpan hasilnya leasehingga 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
BeeOnRope
7
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 MOVdengan 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.
LEAbukan instruksi aritmatika tertentu; ini adalah cara mencegat alamat efektif yang timbul dari salah satu mode pengalamatan memori prosesor.
Misalnya, kita dapat menggunakan LEAhanya 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 LEAterlalu berlebihan salah besar; tanda kurung bukan LEAsintaks 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, LEAtidak berpengaruh pada flag CPU.)
Tidak ada alasan untuk LEAmengecualikan kemungkinan ini hanya karena ada alternatif yang lebih pendek; itu hanya menggabungkan secara ortogonal dengan mode pengalamatan yang tersedia.
// 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
Memecah inline asm Anda menjadi pernyataan terpisah tidak aman, dan daftar clobbers Anda tidak lengkap. Blok dasar-asm memberitahu compiler tidak memiliki clobbers, tetapi sebenarnya memodifikasi beberapa register. Juga, Anda dapat menggunakan =duntuk 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.
Peter Cordes
Jika Anda tidak ingin menulis %%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.
Peter Cordes
@PeterCordes Terima kasih, Peter, apakah Anda ingin saya menghapus jawaban ini atau memodifikasinya setelah komentar Anda?
Jaehyuk Lee
1
Jika Anda memperbaiki inline asm, itu tidak membahayakan dan mungkin membuat contoh konkret yang bagus untuk pemula yang tidak memahami jawaban lainnya. Tidak perlu dihapus, dan ini adalah perbaikan yang mudah seperti yang saya tunjukkan di komentar terakhir saya. Saya pikir akan sangat berharga jika contoh buruk inline asm diperbaiki menjadi contoh "baik". (Saya tidak mengundurkan diri)
Peter Cordes
1
Di mana ada yang mengatakan itu mov 4(%ebx, %eax, 8), %edxtidak valid? Lagi pula, ya, karena movakan 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 = 0xff00000001atau sesuatu. Sebab lea, Anda masih menggunakan ukuran operan 32-bit, sehingga bit tinggi yang tersesat di register input tidak berpengaruh pada hasil 32-bit.
Peter Cordes
4
LEA: hanya instruksi "aritmatika" ..
MOV mentransfer data antar operan tetapi lea hanya menghitung
LEA jelas memindahkan data; memiliki operan tujuan. LEA tidak selalu menghitung; itu menghitung jika alamat efektif yang dinyatakan dalam operan sumber menghitung. LEA EAX, GLOBALVAR tidak menghitung; itu hanya memindahkan alamat GLOBALVAR ke EAX.
Kaz
@ Ka, terima kasih atas tanggapan Anda. sumber saya adalah "LEA (memuat alamat efektif) pada dasarnya adalah instruksi aritmatika — itu tidak melakukan akses memori yang sebenarnya, tetapi umumnya digunakan untuk menghitung alamat (meskipun Anda dapat menghitung bilangan bulat tujuan umum dengan itu)." membentuk Eldad-Eilam buku halaman 149
akuntan
@ Ka: Itu sebabnya LEA berlebihan ketika alamat sudah menjadi konstanta waktu tautan; gunakan mov eax, offset GLOBALVARsaja. Anda dapat menggunakan LEA, tetapi ukuran kode sedikit lebih besar daripada mov r32, imm32dan berjalan pada lebih sedikit port, karena masih melewati proses perhitungan alamat . lea reg, symbolhanya 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.
Peter Cordes
@ Ka: dengan argumen yang sama, Anda bisa mengatakan itu imul eax, edx, 1tidak menghitung: itu hanya menyalin edx ke eax. Tapi sebenarnya itu menjalankan data Anda melalui pengganda dengan latensi 3 siklus. Atau itu rorx eax, edx, 0hanya salinan (rotasikan oleh nol).
Peter Cordes
@PeterCordes Maksud saya adalah bahwa LEA EAX, GLOBALVAL dan MOV EAX, GLOBALVAR hanya mengambil alamat dari operan langsung. Tidak ada pengganda 1, atau offset 0 diterapkan; bisa jadi seperti itu di tingkat perangkat keras tetapi tidak terlihat dalam bahasa assembly atau set instruksi.
Kaz
1
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+1dalam 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 .)
"Ini adalah aturan umum dalam pemrograman perakitan: Gunakan instruksi namun itu mengguncang perahu Anda." Saya pribadi tidak akan memberikan saran itu, karena hal-hal seperti call lbllbl: 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
The6P4C
@ The6P4C Itu adalah peringatan yang berguna. Namun, jika tidak ada alternatif untuk membuat prediksi cabang tidak bahagia, kita harus melakukannya. Ada aturan umum lain dalam pemrograman perakitan. Mungkin ada cara alternatif untuk melakukan sesuatu dan Anda harus memilih dengan bijak dari alternatif. Ada ratusan cara untuk mendapatkan konten register BL ke dalam register AL. Jika sisa RAX tidak perlu dipertahankan, LEA dapat menjadi pilihan. Tidak memengaruhi flag mungkin merupakan ide bagus pada beberapa dari ribuan jenis prosesor x86. Groetjes Albert
Albert van der Horst
-1
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:
"Alamat efektif" hanyalah bagian "offset" dari suatu seg:offpasangan. LEA tidak terpengaruh oleh basis segmen; kedua instruksi tersebut akan (secara tidak efisien) dimasukkan 0x1234ke dalam AX. x86 sayangnya tidak memiliki cara mudah untuk menghitung alamat linear lengkap (basis + segmen efektif) menjadi register atau pasangan-pasangan.
Peter Cordes
@PeterCordes Sangat berguna, terima kasih sudah mengoreksi saya.
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:
Sekarang bayangkan sebuah pernyataan seperti:
dimana
points[]
array dariPoint
. Dengan asumsi basis array sudah dalamEBX
, dan variabeli
dalamEAX
, danxcoord
danycoord
masing-masing 32 bit (demikianycoord
juga pada offset 4 byte dalam struct), pernyataan ini dapat dikompilasi ke:yang akan mendarat
y
diEDX
. Faktor skala 8 adalah karena masingPoint
- masing berukuran 8 byte. Sekarang perhatikan ungkapan yang sama yang digunakan dengan operator "alamat" &:Dalam hal ini, Anda tidak ingin nilai
ycoord
, tetapi alamatnya. Di situlahLEA
(memuat alamat efektif) masuk. Alih-alihMOV
, kompiler dapat menghasilkanyang akan memuat alamat di
ESI
.sumber
mov
instruksi dan meninggalkan tanda kurung?MOV EDX, EBX + 8*EAX + 4
MOV
dengan sumber tidak langsung, kecuali itu hanya tipuan dan bukanMOV
. Sebenarnya tidak membaca dari alamat yang dihitung, hanya menghitungnya.Dari "Zen Majelis" oleh Abrash:
Dan
LEA
tidak mengubah bendera.Contohnya
LEA EAX, [ EAX + EBX + 1234567 ]
menghitungEAX + EBX + 1234567
(itulah tiga operan)LEA EAX, [ EBX + ECX ]
menghitungEBX + 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 ]
danINC EAX
adalah bahwa yang terakhir berubahEFLAGS
tetapi yang pertama tidak; ini mempertahankanCMP
keadaan.sumber
LEA EAX, [ EAX + EBX + 1234567 ]
menghitung jumlahEAX
,EBX
dan1234567
(itulah tiga operan).LEA EAX, [ EBX + ECX ]
menghitungEBX + ECX
tanpa mengesampingkan baik dengan hasilnya. Hal ketiga yangLEA
digunakan untuk (tidak terdaftar oleh Frank) adalah perkalian dengan konstanta (oleh dua, tiga, lima atau sembilan), jika Anda menggunakannya sepertiLEA EAX, [ EBX + N * EBX ]
(N
bisa 1,2,4,8). Usecase lain berguna dalam loop: perbedaan antaraLEA EAX, [ EAX + 1 ]
danINC EAX
adalah bahwa yang terakhir berubahEFLAGS
tetapi yang pertama tidak; ini mempertahankanCMP
negaraLEA
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 sepertiCF
danZF
, saat menghitung alamat dengan instruksi aritmatika sukaADD
atauMUL
tidak. Fitur ini mengurangi tingkat ketergantungan di antara instruksi dan dengan demikian membuat ruang untuk optimasi lebih lanjut oleh kompiler atau penjadwal perangkat keras.sumber
lea
kadang berguna bagi kompiler (atau pengode manusia) untuk melakukan matematika tanpa mengalahkan hasil flag. Tetapilea
tidak lebih cepat dari ituadd
. 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:
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).
sumber
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.
sumber
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 menggunakanmul
instruksi yang lebih megah dan lebih lambat.lea eax,[eax*3]
akan menerjemahkan ke setara denganlea 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:untuk memindahkan item
ebx
pointereax
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.sumber
Alasan terbesar yang Anda gunakan di
LEA
atasMOV
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
sepertiMOV
tetapi Anda tidak benar-benar mengingat memori. Dengan kata lain:MOV EAX, [ESP+4]
Ini akan memindahkan konten
ESP+4
ke poin manaEAX
.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 aMOV
terbatas pada penambahan / pengurangan.sumber
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:
diimplementasikan hampir identik secara internal. Perbedaannya adalah langkah yang dilompati. Kedua instruksi tersebut bekerja seperti:
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:Jika seseorang ingin menggunakan sesuatu seperti stosw untuk menyimpan data ke alamat relatif BP, bisa mengatakannya
lebih nyaman daripada:
Perhatikan bahwa melupakan dunia "offset" akan menyebabkan konten lokasi [BP + 8], bukannya nilai 8, ditambahkan ke DI. Ups.
sumber
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 (termasukLEA
dan alamat referensi memori lainnya), ini berarti operasi aritmatikaLEA
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 referensiMemoryAddress
. Ini berbeda dariMOV REG, MemoryAddress
yang mengkodekan alamat virtual relatif dan memerlukan relokasi / patching dalam sistem operasi modern (seperti ASLR adalah fitur umum). JadiLEA
dapat digunakan untuk mengkonversi non PIC ke PIC.sumber
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 menjalankanadd
atausub
atau sebagian besar operasi aritmatika dasar lainnya pada empat ALU yang berbeda , tetapi hanya dapat mengeksekusilea
pada satu (komplekslea
) atau dua (sederhanalea
). Lebih penting lagi, dualea
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.
sumber
[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 menambahesi
nilaiecx
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 menulisadd eax, [esi + 4200]; add ebx, [esi + 4200]; add ecx, [esi + 4200]
, Anda harus memilihlea 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 satulea
, 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. @KazInstruksi 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:
itu memindahkan isi dari lokasi memori yang ditunjuk ke register target.
Jika kita mengganti
MOV
denganLEA
, 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:Ini valid; kita dapat mengujinya di Linux prompt:
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 bukanLEA
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:
Ini encoding yang sangat mirip, lihat? Hanya
8d
dariLEA
telah berubah menjadi8b
.Tentu saja,
LEA
pengkodean ini lebih lama daripada memindahkan nol langsung keEAX
: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.sumber
Berikut ini sebuah contoh.
Dengan -O (optimisasi) sebagai opsi kompiler, gcc akan menemukan instruksi lea untuk baris kode yang ditunjukkan.
sumber
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.
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
Hasil eksekusi
sumber
=d
untuk memberi tahu kompiler bahwa hasilnya ada di EDX, menyimpan amov
. 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. sepertiasm("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, karenamov
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 digunakanmov $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. Sebablea
, 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
sumber
mov eax, offset GLOBALVAR
saja. Anda dapat menggunakan LEA, tetapi ukuran kode sedikit lebih besar daripadamov 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 iturorx 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
dan
memiliki efek yang sama pada AX tetapi tidak pada bendera status. (Ini adalah notasi ciasdis .)
sumber
call lbl
lbl: pop rax
"bekerja" secara teknis sebagai cara untuk mendapatkan nilairip
, 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 perkirakanMaafkan 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:
dan
sumber
seg:off
pasangan. LEA tidak terpengaruh oleh basis segmen; kedua instruksi tersebut akan (secara tidak efisien) dimasukkan0x1234
ke dalam AX. x86 sayangnya tidak memiliki cara mudah untuk menghitung alamat linear lengkap (basis + segmen efektif) menjadi register atau pasangan-pasangan.