Apa tujuan dari instruksi LEA?

676

Bagi saya, sepertinya MOV yang funky. Apa tujuannya dan kapan saya harus menggunakannya?

pengguna200557
sumber
2
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

LEA ESI, [EBX + 8*EAX + 4]

yang akan memuat alamat di ESI.

IJ Kennedy
sumber
112
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:

  1. kemampuan untuk melakukan penambahan dengan dua atau tiga operan, dan
  2. kemampuan untuk menyimpan hasil dalam register apa pun ; bukan hanya salah satu dari operan sumber.

Dan LEAtidak mengubah bendera.

Contohnya

  • LEA EAX, [ EAX + EBX + 1234567 ]menghitung EAX + EBX + 1234567(itulah tiga operan)
  • 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.

Frank Krueger
sumber
42
@AbidRahmanK beberapa contoh: LEA EAX, [ EAX + EBX + 1234567 ]menghitung jumlah EAX, EBXdan 1234567(itulah tiga operan). LEA EAX, [ EBX + ECX ]menghitung EBX + ECX tanpa 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.

Angus Lee
sumber
1
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).

hdante
sumber
8
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.

LEA EAX, [EAX * 2 + EAX]   ;EAX = EAX * 3
LEA EAX, [EAX * 4 + EAX]   ;EAX = EAX * 5
LEA EAX, [EAX * 8 + EAX]   ;EAX = EAX * 9
GJ.
sumber
13
+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.

Mehrdad Afshari
sumber
23

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.

David Hoelzer
sumber
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.

supercat
sumber
12

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.

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, 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.

Thomson
sumber
2
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.

nasihat
sumber
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.)

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 8ddari LEAtelah berubah menjadi 8b.

Tentu saja, LEApengkodean 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 LEAmengecualikan kemungkinan ini hanya karena ada alternatif yang lebih pendek; itu hanya menggabungkan secara ortogonal dengan mode pengalamatan yang tersedia.

Kaz
sumber
6

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.

pengguna3634373
sumber
6

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
Jaehyuk Lee
sumber
1
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

Akuntan
sumber
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 .)

Albert van der Horst
sumber
"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 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
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:

LEA AX, DS:[0x1234]

dan

LEA AX, CS:[0x1234]
tzoz
sumber
1
"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.
tzoz