LEA talimatının amacı nedir?


676

Benim için korkak bir MOV gibi görünüyor. Amacı nedir ve ne zaman kullanmalıyım?


2
Ayrıca bkz . Adres / işaretçi olmayan değerlerde LEA kullanma? : LEA sadece bir kaydırma ve ekleme talimatıdır. Muhtemelen 8086'ya eklenmiştir, çünkü donanım adresleme modlarının kodunu çözmek ve hesaplamak için oradadır, çünkü sadece adreslerle kullanılmak üzere "amaçlanmıştır". Unutmayın, işaretçiler sadece montajda tamsayılar.
Peter Cordes

Yanıtlar:


798

Diğerlerinin de belirttiği gibi, LEA (yüke etkili adres) genellikle belirli hesaplamaları yapmak için bir "hile" olarak kullanılır, ancak bu birincil amacı değildir. X86 komut seti, dizilerin - özellikle de dizilerin veya küçük yapıların dizilerinin - yaygın olduğu Pascal ve C gibi üst düzey dilleri desteklemek üzere tasarlanmıştır. Örneğin, (x, y) koordinatlarını temsil eden bir yapı düşünün:

struct Point
{
     int xcoord;
     int ycoord;
};

Şimdi şöyle bir ifade hayal edin:

int y = points[i].ycoord;

points[]dizisi nerede Point. Dizinin bir taban varsayıldığında zaten EBXve değişken iolan EAXve xcoordve ycoordher biri 32 bit (so ycoordyapı ofset 4 bayt olduğunu), bu açıklama için derlenebilir:

MOV EDX, [EBX + 8*EAX + 4]    ; right side is "effective address"

hangi girerler yiçinde EDX. 8 ölçek faktörü, her Pointbirinin 8 bayt boyutunda olmasıdır. Şimdi, "operatörünün" adresi "ile kullanılan ifadenin aynısını düşünün:

int *p = &points[i].ycoord;

Bu durumda, değerini ycoorddeğil, adresini istersiniz . İşte burada LEA(yük etkili adres) devreye girer. A yerine MOVderleyici

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

adresi yükler ESI.


112
movTalimatı uzatmak ve destekleri bırakmak daha temiz olmaz mıydı ? MOV EDX, EBX + 8*EAX + 4
Natan Yellin

14
@imacake LEA'yı özel bir MOV ile değiştirerek sözdizimini temiz tutarsınız: [] parantezleri her zaman C'de bir imlecin kaydının silinmesine eşdeğerdir. Parantez olmadan her zaman işaretçinin kendisi ile ilgilenirsiniz.
Natan Yellin

139
MOV komutunda matematik yapmak (EBX + 8 * EAX + 4) geçerli değildir. LEA ESI, [EBX + 8 * EAX + 4] geçerlidir, çünkü bu x86'nın desteklediği bir adresleme modudur. en.wikipedia.org/wiki/X86#Addressing_modes
Erik

29
@JonathanDickinson LEA MOVdolaylı bir kaynağı olan bir gibidir , ancak sadece dolaylı değil MOV. Aslında hesaplanan adresten okunmaz , sadece hesaplar.
Ocaklar

24
Erik, tur yorumu doğru değil. MOV eax, [ebx + 8 * ecx + 4] geçerlidir. Bununla birlikte MOV, bellek konumunun içeriğini döndürürken, LEA adresi döndürür
Olorin

562

Gönderen "Meclis Zen" Abrash tarafından:

LEA, bellek adresleme hesaplamaları yapan ancak gerçekte belleğe hitap etmeyen tek talimattır. LEAstandart bir bellek adresleme işlenenini kabul eder, ancak hesaplanan bellek ofsetini belirtilen kayıt defterinde saklamaktan başka bir şey yapmaz, bu herhangi bir genel amaçlı kayıt olabilir.

Bu bize ne veriyor? Sağlanmayan iki şey ADD:

  1. iki veya üç işlenenle ekleme yapma yeteneği ve
  2. sonucu herhangi bir kayıtta saklama yeteneği ; sadece kaynak işlenenlerden biri değil.

Ve LEAbayrakları değiştirmez.

Örnekler

  • LEA EAX, [ EAX + EBX + 1234567 ]hesaplar EAX + EBX + 1234567(bu üç işlenen)
  • LEA EAX, [ EBX + ECX ]EBX + ECXsonucu ile geçersiz kılmadan hesaplar .
  • sabit olarak çarpma (iki, üç, beş veya dokuz), eğer böyle kullanırsanız LEA EAX, [ EBX + N * EBX ](N 1,2,4,8 olabilir).

Diğer Usecase döngülerinde kullanışlıdır: arasındaki fark LEA EAX, [ EAX + 1 ]ve INC EAXbu ikinci bir değişiklik olduğunu EFLAGS, ancak eski değildir; bu CMPdevleti korur .


42
: Bazı örnekler @AbidRahmanK LEA EAX, [ EAX + EBX + 1234567 ]toplamını hesaplar EAX, EBXve 1234567(üç işlenen s). LEA EAX, [ EBX + ECX ]hesaplar EBX + ECX olmayan sonucu ile ya da geçersiz kılma. Üçüncü şey LEA(Frank tarafından listelenmeyen) 'dir kullanılır sabiti ile çarpma (iki, üç, beş veya dokuz tarafından) sizin gibi kullanırsanız, LEA EAX, [ EBX + N * EBX ]( N1,2,4,8 olabilir). Diğer Usecase döngülerinde kullanışlıdır: arasındaki fark LEA EAX, [ EAX + 1 ]ve INC EAXbu ikinci bir değişiklik olduğunu EFLAGS, ancak eski değildir; bu CMPdevleti korur
FrankH.

@FrankH. Hala anlamıyorum, bu yüzden başka bir yere bir işaretçi yüklüyor?

6
@ ripDaddy69 evet, bir çeşit - eğer "yük" demek demek "adres hesaplama / işaretçi aritmetiği yapar". It does erişim belleği değil (yani değil "KQUEUE" o C programlama açısından denilen olurdum olarak işaretçi).
FrankH.

2
1: Bu neyin hile 'tür açık hale getirir LEA( ") LEA (yük etkili adresine genellikle olarak kullanılır 'bölümüne bakın yukarıdaki IJ Kennedy popüler cevap' belirli hesaplamaları yapmak için" hile) için ... kullanılabilir
Esad'a Ebrahim

3
Hızlı olan 2 işlenen LEA ile yavaş olan 3 işlenen LEA arasında büyük bir fark vardır. Intel Optimization manual, hızlı yol LEA'nın tek döngü olduğunu ve yavaş yol LEA'nın üç döngü sürdüğünü söylüyor. Dahası, Skylake'te iki hızlı yol fonksiyonel birimi (port 1 ve 5) vardır ve sadece bir yavaş yol fonksiyonel birimi (port 1) vardır. Kılavuzdaki Derleyici / Derleyici Kodlama Kural 33, 3 işlenen LEA kullanımına karşı bile uyarıda bulunur.
Olsonist

110

Bir diğer önemli özelliği LEAtalimatı bu gibi durum kodlarını değiştirmek olmamasıdır CFve ZFbenzeri aritmetik talimatlarla adresini hesaplama sırasında, ADDya da MULyok. Bu özellik, talimatlar arasındaki bağımlılık seviyesini azaltır ve böylece derleyici veya donanım zamanlayıcı tarafından daha fazla optimizasyon için yer açar.


1
Evet, leabazen derleyicinin (veya insan kodlayıcısının) bir bayrak sonucunu sarsmadan matematik yapması için yararlıdır. Ama leabundan daha hızlı değil add. Çoğu x86 talimatı bayrak yazar. Yüksek performanslı x86 uygulamaları, EFLAGS'ı yeniden adlandırmalı veya normal kodun hızlı çalışması için yazma sonrası yazma tehlikesinden kaçınmalıdır, bu nedenle bayrak yazmalarını önleyen talimatlar bundan dolayı daha iyi değildir. ( kısmi bayrak işleri sorun yaratabilir, bkz. INC talimat vs EKLE 1: Önemli mi? )
Peter Cordes

2
@PeterCordes: Bunu buraya getirmekten nefret ediyorum ama - bu yeni [x86-lea] etiketinin gereksiz ve gereksiz olduğunu düşünmekte yalnız mıyım?
Michael Petch

2
@MichaelPetch: Evet, bence çok özel. Makine dilini anlamayan yeni başlayanları karıştırıyor ve her şeyin (işaretçiler dahil) sadece bit / bayt / tamsayı olduğunu, bu yüzden çok sayıda oyla çok fazla soru var. Ancak bunun için bir etikete sahip olmak, aslında sadece kopya olmayan yaklaşık 2 veya 3 toplam olduğunda, açık uçlu gelecek sorulara yer olduğu anlamına gelir. (nedir? Tamsayıların çarpımı için nasıl kullanılır? ve AGU'lara karşı ALU'larda ve hangi gecikme / verim ile dahili olarak çalıştığı. Ve belki de "amaçlanan" amaç)
Peter Cordes

@PeterCordes: Kabul ediyorum ve tüm bu yayınların düzenlenmiş bir şey varsa, LEA ile ilgili mevcut soruların birkaçının neredeyse bir kopyası. Bir etiket yerine, kopyalar tanımlanmalı ve imho olarak işaretlenmelidir.
Michael Petch

1
@EvanCarroll: henüz bitirmediyseniz tüm LEA sorularını etiketlemeye devam edin. Yukarıda tartışıldığı gibi, x86-lea'nin bir etiket için çok spesifik olduğunu düşünüyoruz ve gelecekteki yinelenmeyen sorular için çok fazla alan yok. Bunun için bir sürü iş olacağını düşünüyorum aslında çoğu için olsa da, ya da aslında hiç birleştirme için mods almak için hangilerinin karar bir dup hedef olarak "en iyi" Q & A seçin.
Peter Cordes

93

Tüm açıklamalara rağmen, LEA aritmetik bir işlemdir:

LEA Rt, [Rs1+a*Rs2+b] =>  Rt = Rs1 + a*Rs2 + b

Sadece bir vardiya + ekleme işlemi için adının aşırı aptalca olması. Bunun nedeni en yüksek puanlı cevaplarda zaten açıklanmıştı (yani, yüksek düzeyli bellek referanslarını doğrudan eşleştirmek için tasarlanmıştır).


8
Ve aritmetik adres hesaplama donanımı tarafından gerçekleştirilir.
Ben Voigt

30
@BenVoigt Bunu söylerdim, çünkü ben eski bir adamım :-) Geleneksel olarak, x86 işlemciler bunun için adresleme birimlerini kullandılar, kabul ettiler. Ancak "ayrılık" bugünlerde çok bulanıklaştı. Bazı CPU'lar artık özel olarak ayrılmış AGU'lara sahip değildir, diğerleri LEAise AGU'larda değil, sıradan tamsayı ALU'larında yürütülmeyi seçmiştir . Biri "şeyler nerede çalışır" bulmak için bu gün CPU özelliklerini çok yakından okumak zorunda ...
FrankH.

2
@FrankH .: sıra dışı CPU'lar genellikle ALU'larda LEA çalıştırırken, bazı sıralı CPU'lar (Atom gibi) bazen AGU'larda çalıştırır (çünkü bir bellek erişimiyle meşgul olamazlar).
Peter Cordes

3
Hayır, isim aptal değil. LEAbellekle ilgili herhangi bir adresleme modundan kaynaklanan adresi verir. Bir vardiya ve ekleme işlemi değildir.
Kaz

3
FWIW, AGU'da işlemi gerçekleştiren çok az (varsa) mevcut x86 CPU vardır. Çoğu ya da hepsi sadece diğer aritmetik işlemlerde olduğu gibi bir ALU kullanır.
BeeOnRope

77

Belki sadece LEA eğitimi hakkında başka bir şey. LEA'yı kayıtları 3, 5 veya 9 ile hızlı çarpmak için de kullanabilirsiniz.

LEA EAX, [EAX * 2 + EAX]   ;EAX = EAX * 3
LEA EAX, [EAX * 4 + EAX]   ;EAX = EAX * 5
LEA EAX, [EAX * 8 + EAX]   ;EAX = EAX * 9

13
Hüner için +1. Ama bir soru sormak istiyorum (aptal olabilir), neden böyle üçle doğrudan çarpmıyorsunuz LEA EAX, [EAX*3]?
Abid Rahman K

13
@Abid Rahman K: Unde x86 CPU komut seti talimatı yoktur.
GJ.

50
@AbidRahmanK intel asm sözdizimine bir çarpma gibi gelmesine rağmen, lea komutu yalnızca vardiya işlemlerini kodlayabilir. Opcode, kaymayı tanımlamak için 2 bite sahiptir, bu nedenle yalnızca 1,2,4 veya 8 ile çarpabilirsiniz.
ithkuil

6
@Koray Tugay: Kayıtları 2,4,8,16 ile shlçarpmak için sola kaydırma gibi talimatlar kullanabilirsiniz ... daha hızlı ve daha kısa. Ancak 2'nin gücünden farklı sayılarla çarpmak için normalde muldaha iddialı ve daha yavaş olan talimatları kullanırız .
GJ.

7
@GJ. böyle bir kodlama olmamasına rağmen, bazı montajcılar bunu bir kısayol olarak kabul eder, örneğin fasm. Yani örneğin lea eax,[eax*3]eşdeğerine çevirir lea eax,[eax+eax*2].
Ruslan

59

lea"etkin yükleme adresi" nin kısaltmasıdır. Kaynak işlenen tarafından konum referansının adresini hedef işlenene yükler. Örneğin, aşağıdakileri yapmak için kullanabilirsiniz:

lea ebx, [ebx+eax*8]

ebxişaretçi eaxöğelerini (64 bit / öğe dizisinde) tek bir komutla daha ileriye taşımak için . Temel olarak, işaretçileri verimli bir şekilde değiştirmek için x86 mimarisi tarafından desteklenen karmaşık adresleme modlarından faydalanırsınız.


23

A LEAüzerinde kullanmanızın en büyük nedeni MOV, adresi hesaplamak için kullandığınız kayıtlarda aritmetik işlem yapmanız gerektiğidir. Etkili bir şekilde, bir çok kayıt üzerinde işaretçi aritmetiğinin ne kadar etkili olduğunu "serbest" olarak etkili bir şekilde gerçekleştirebilirsiniz.

Bu konuda gerçekten kafa karıştırıcı olan şey, LEAtıpkı bir gibi bir MOVşey yazmanızdır, ancak aslında belleği derefere etmiyorsunuzdur. Başka bir deyişle:

MOV EAX, [ESP+4]

Bu, neyi ESP+4işaret ettiğinin içeriğini taşıyacaktır EAX.

LEA EAX, [EBX*8]

Bu, etkin adresi EBX * 8o konumda bulunanlara değil EAX'e taşır . Gördüğünüz gibi, a MOV, toplama / çıkarma ile sınırlıyken , iki faktörle (ölçeklendirme) çarpmak mümkündür .


Herkese özür dilerim. @ big.heart bu üç saat önce bir cevap vererek beni kandırdı ve Meclis soru araştırmamda "yeni" olarak görünmesini sağladı.
David Hoelzer

1
Sözdizimi neden bellek adresleme yapmadığında parantez kullanır?
17'de golopot

3
@ q4w56 Bu sorunun cevabının bulunduğu şeylerden biri, "İşte böyle yapıyorsunuz." İnsanların ne yaptığını anlamakta zorlandıkları nedenlerden biri olduğuna inanıyorum LEA.
David Hoelzer

2
@ q4w56: bellek operandı sözdizimi ve makine kodu kodlamasını kullanan bir shift + add komutudur . Bazı CPU'larda AGU donanımını bile kullanabilir, ancak bu tarihsel bir ayrıntıdır. Hala ilgili olan, dekoder donanımının bu tür shift + add kodunu çözmek için zaten mevcut olması ve LEA'nın bunu bellek adresleme yerine aritmetik için kullanmamıza izin vermesidir. (Veya bir giriş gerçekten bir işaretçi ise adres hesaplamaları için).
Peter Cordes

20

8086, bir kayıt işlenenini ve etkili bir adresi kabul eden, bu etkili adresin ofset kısmını hesaplamak için bazı hesaplamalar yapan ve hesaplanan adres tarafından atıfta bulunulan kayıt ve hafızayı içeren bazı işlemleri gerçekleştiren geniş bir talimat ailesine sahiptir. Bu ailenin talimatlarından birinin, gerçek hafıza işlemini atlamak dışında yukarıdaki gibi davranması oldukça basitti. Bu, talimatlar:

mov ax,[bx+si+5]
lea ax,[bx+si+5]

neredeyse aynı şekilde dahili olarak uygulandı. Fark atlanan bir adımdır. Her iki talimat da şu şekilde çalışır:

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

Intel'in neden bu talimatın dahil edilmeye değer olduğunu düşündüğüne gelince, tam olarak emin değilim, ancak uygulamanın ucuz olması büyük bir faktör olurdu. Başka bir faktör, Intel'in toplayıcısının sembollerin BP kaydına göre tanımlanmasına izin vermesiydi. Eğer fnordbir BP-nispi sembolü olarak tanımlandı (örneğin BP + 8) denebilir:

mov ax,fnord  ; Equivalent to "mov ax,[BP+8]"

Bir kişi BP göreli bir adrese veri depolamak için stosw gibi bir şey kullanmak isterse,

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

daha uygun:

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

Dünya "ofset" inin unutulmasının DI değerine 8 değeri yerine [BP + 8] içeriğinin eklenmesine neden olacağını unutmayın. Hata.


12

Bahsedilen mevcut cevaplar, LEAbelleğe erişmeden bellek adresleme aritmetiği gerçekleştirmenin avantajlarına sahiptir, aritmetik sonucu basit ekleme talimatı yerine farklı bir kayıt defterine kaydeder. Temel performans avantajı, modern işlemcinin etkili adres üretimi için ayrı bir LEA ALU birimi ve bağlantı noktasına sahip olmasıdır ( LEAve diğer bellek referans adresi dahil), bu, ALU'daki LEAaritmetik işlemin ve ALU'daki diğer normal aritmetik işlemin bir arada paralel olarak yapılabileceği anlamına gelir. çekirdek.

LEA birimi hakkında bazı ayrıntılar için Haswell mimarisinin bu makalesine bakın: http://www.realworldtech.com/haswell-cpu/4/

Diğer cevaplarda belirtilmeyen bir diğer önemli nokta, LEA REG, [MemoryAddress]talimattır, bu talimattaki PC'nin göreceli adresini referans olarak kodlayan PIC'dir (konumdan bağımsız kod) MemoryAddress. Bu, MOV REG, MemoryAddressgöreli sanal adresi kodlayandan farklıdır ve modern işletim sistemlerinde (ASLR ortak özelliktir) yer değiştirme / yama gerektirmektedir. Bu LEAtür PIC olmayanları PIC'ye dönüştürmek için kullanılabilir.


2
"Ayrı LEA ALU" kısmı çoğunlukla yanlıştır. Modern CPU'lar lea, diğer aritmetik talimatları yürüten aynı ALU'ların biri veya daha fazlasında yürütülür (ancak genellikle diğer aritmetik işlemlerden daha azdır). Örneğin, bahsedilen Haswell CPU dört farklı ALU üzerinde veya diğer temel aritmetik işlemleri gerçekleştirebilir addveya sadece bir (karmaşık ) veya iki (basit ) işlem yapabilir. Daha da önemlisi, bu iki yetenekli ALU, diğer talimatları yürütebilecek olan dördünden sadece ikisidir, bu nedenle iddia edildiği gibi paralellik faydası yoktur. sublealealealea
BeeOnRope

Bağladığınız makale (doğru) LEA'nın bir tamsayı ALU (add / sub / boolean) ve Haswell'deki tamsayı MUL birimi ile aynı bağlantı noktasında olduğunu gösterir. (Ve FP ADD / MUL / FMA dahil vektör ALU'ları). Yalnızca basit LEA birimi, ADD / SUB / whatever ve vector shuffles ve diğer şeyleri çalıştıran bağlantı noktası 5 üzerindedir. İndirememememin tek nedeni RIP göreli LEA (yalnızca x86-64 için) kullanımını belirtmenizdir.
Peter Cordes

8

LEA komutu, CPU tarafından etkili adreslerin zaman alıcı hesaplamalarını önlemek için kullanılabilir. Bir adres tekrar tekrar kullanılıyorsa, etkin adresi her kullanıldığında hesaplamak yerine bir kayıt defterinde saklamak daha etkilidir.


Modern x86'da olması gerekmez. Adresleme modlarının çoğu, bazı uyarılarla aynı maliyete sahiptir. Bu yüzden [esi]söylemek nadiren daha ucuz [esi + 4200]ve nadiren daha ucuzdur [esi + ecx*8 + 4200].
BeeOnRope

@BeeOnRope [esi]daha ucuz değil [esi + ecx*8 + 4200]. Ama neden karşılaştırma zahmetine giriyorsun? Eşdeğer değiller. Birincisinin ikincisi ile aynı bellek konumunu belirlemesini istiyorsanız, ek talimatlara ihtiyacınız vardır: 8 esiile ecxçarpma değerine eklemeniz gerekir . Daha sonra 4200'ü eklemeniz gerekir. Bu ek talimatlar kod boyutuna eklenir (talimat önbelleğinde yer kaplar, getirilecek döngüler).
Kaz

2
@Kaz - Sanırım benim fikrimi kaçırdın (veya OP'nin noktasını kaçırdım). Anladığım kadarıyla, OP, [esi + 4200]bir dizi talimatta tekrar tekrar gibi bir şey kullanacaksanız , önce etkili adresi bir kayıt defterine yüklemek ve kullanmak daha iyi olduğunu söylüyor. Örneğin, yazmak yerine , nadiren daha hızlı olan add eax, [esi + 4200]; add ebx, [esi + 4200]; add ecx, [esi + 4200]tercih etmelisiniz lea edi, [esi + 4200]; add eax, [edi]; add ebx, [edi]; add ecx, [edi]. En azından bu cevabın açık yorumu.
BeeOnRope

Ben karşılaştırıyordum nedenle Yani [esi]ve [esi + 4200](veya [esi + ecx*8 + 4200](Anladığım kadarıyla) bu OP önerdiği sadeleştirme olmasıdır: Aynı kompleks adresi ile N talimatları basit (tek reg) adresleme, artı bir N talimatları dönüşmelerini lea, çünkü karmaşık adresleme "zaman alıcıdır" Aslında, modern
x86'da

1
Belki bir kayıt baskısını hafifletirsiniz, evet - ama tam tersi olabilir: eğer etkili adresi oluşturduğunuz kayıtlar canlıysa, sonucu kaydetmek için başka bir kayıt gerekir lea, bu durumda basıncı arttırır. Genel olarak, ara maddelerin depolanması kayıt baskısının bir nedenidir, bunun için bir çözüm değildir - ancak çoğu durumda bir yıkama olduğunu düşünüyorum. @Kaz
BeeOnRope

7

LEA (Etkin Adres Yükle) talimatı, Intel işlemcinin bellek adresleme modlarından herhangi birinden kaynaklanan adresi almanın bir yoludur.

Yani, böyle bir veri taşıma işlemimiz varsa:

MOV EAX, <MEM-OPERAND>

belirlenen bellek konumunun içeriğini hedef yazmacıya taşır.

Biz değiştirirseniz MOVbiriminin by LEAardından hafıza yerin adresi ile aynı şekilde hesaplanır <MEM-OPERAND>adresleme ifadesi. Ancak bellek konumunun içeriği yerine, konumun kendisini hedefe alırız.

LEAspesifik bir aritmetik komut değildir; işlemcinin bellek adresleme modlarından herhangi birinden kaynaklanan etkili adresi yakalamanın bir yoludur.

Örneğin LEA, sadece basit bir doğrudan adres üzerinde kullanabiliriz . Hiçbir aritmetik söz konusu değildir:

MOV EAX, GLOBALVAR   ; fetch the value of GLOBALVAR into EAX
LEA EAX, GLOBALVAR   ; fetch the address of GLOBALVAR into EAX.

Bu geçerlidir; Linux isteminde test edebiliriz:

$ 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

Burada ölçeklendirilmiş bir değer ve ofset yoktur. Sıfır EAX'a taşınır. Bunu anında bir işlenenle MOV kullanarak yapabiliriz.

Köşeli parantezlerin LEAgereksiz olduğunu düşünen insanların ciddi şekilde yanlış olmasının nedeni budur ; köşeli ayraçlar LEAsözdizimi değildir ancak adresleme modunun bir parçasıdır.

LEA donanım düzeyinde gerçektir. Oluşturulan talimat, gerçek adresleme modunu kodlar ve işlemci, adresi hesaplama noktasına kadar gerçekleştirir. Sonra bu adresi bir bellek başvurusu oluşturmak yerine hedefe taşır. (Başka bir talimatta adresleme modunun adres hesaplamasının CPU bayrakları LEAüzerinde hiçbir etkisi olmadığından, CPU bayrakları üzerinde hiçbir etkisi yoktur.)

Sıfır adresinden değeri yükleme ile kontrast:

$ as
movl 0, %eax
$ objdump -d a.out | grep mov
   0:   8b 04 25 00 00 00 00    mov    0x0,%eax

Çok benzer bir kodlama, anlıyor musunuz? Sadece 8dof LEAolarak değişti 8b.

Tabii ki, bu LEAkodlama hemen sıfırın içine taşınmasından daha uzundur EAX:

$ as
movl $0, %eax
$ objdump -d a.out | grep mov
   0:   b8 00 00 00 00          mov    $0x0,%eax

LEAAncak daha kısa bir alternatif olduğu için bu olasılığı dışlamak için hiçbir neden yoktur ; sadece mevcut adresleme modlarıyla dikey bir şekilde birleşiyor.


6

İşte bir örnek.

// 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;
}

Derleyici seçeneği olarak -O (optimizasyon) ile gcc, belirtilen kod satırı için lea komutunu bulacaktır.


6

Görünüşe göre çok sayıda cevap zaten tamamlandı, ben aynı ifade biçimine sahip olduğunda lea ve move komutunun nasıl farklı çalıştığını göstermek için bir örnek kod eklemek istiyorum.

Uzun bir hikaye kısaca anlatmak için, hem talimat hem de mov talimatları, talimatların src işlenenini içeren parantezlerle birlikte kullanılabilir. Bunlar bir arada yer alan zaman () , ekspresyon () aynı şekilde hesaplanır; ancak, iki komut src işlenenindeki hesaplanan değeri farklı bir şekilde yorumlar.

İfade, lea veya mov ile kullanıldığında, src değeri aşağıdaki gibi hesaplanır.

D (Rb, Ri, S) => (Reg [Rb] + S * Reg [Ri] + D)

Ancak, mov komutuyla birlikte kullanıldığında, yukarıdaki ifade tarafından üretilen adresin işaret ettiği değere erişmeye ve onu hedefe depolamaya çalışır.

Bunun aksine, lea komutu yukarıdaki ifadeyle yürütüldüğünde, üretilen değeri hedefe olduğu gibi yükler.

Aşağıdaki kod, lea komutunu ve mov komutunu aynı parametre ile yürütür. Ancak, farkı yakalamak için, mov komutunun bir sonucu olarak yanlış bir adrese erişmenin neden olduğu segmentasyon hatasını yakalamak için bir kullanıcı seviyesi sinyal işleyicisi ekledim.

Örnek kod

#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
          );
}

Yürütme sonucu

Result of lea instruction: 14
inst at 0x4007b5 tries to access memory at 14, but failed

1
Satır içi grubunuzu ayrı ifadelere bölmek güvenli değildir ve clobbers listeleriniz eksiktir. Basic-asm bloğu derleyicinin clobbers olmadığını söyler, ancak aslında birkaç kaydı değiştirir. Ayrıca, =dderleyiciye sonucun EDX'te olduğunu söylemek için kullanabilirsiniz , a mov. Ayrıca çıktıda erken bir gecikme bildirimi bıraktınız. Bu, göstermeye çalıştığınız şeyi gösterir, ancak aynı zamanda diğer bağlamlarda kullanıldığında kırılacak olan yanıltıcı kötü bir inline asm örneğidir. Bir yığın taşması cevabı için bu Kötü Bir Şey.
Peter Cordes

%%Tüm bu kayıt adlarına Genişletilmiş asm'de yazmak istemiyorsanız , giriş kısıtlamalarını kullanın. gibi asm("lea 4(%%ebx, %%eax, 8), %%edx" : "=d"(result_of_lea) : "a"(1), "b"(2));. Derleyicinin kayıtlarını başlatmasına izin vermek, clobbers bildirmek zorunda olmadığınız anlamına da gelir. Mov-instant tüm kaydın üzerine yazmadan önce xor-zeroing ile işleri çok karmaşık hale getiriyorsunuz.
Peter Cordes

@PeterCordes Teşekkürler, Peter, bu cevabı silmemi veya yorumların ardından değiştirmemi ister misin?
Jaehyuk Lee

1
Satır içi topluluğu düzeltirseniz, herhangi bir zarar vermez ve belki de diğer cevapları anlamayan yeni başlayanlar için iyi bir somut örnek oluşturur. Silmeye gerek yok ve son yorumumda gösterdiğim gibi kolay bir düzeltme. Inline asm'ın kötü örneğinin "iyi" bir örneğe sabitlenmesinin bir artışa değer olacağını düşünüyorum. (İndirmedim)
Peter Cordes

1
Herkes bunun mov 4(%ebx, %eax, 8), %edxgeçersiz olduğunu nereden söylüyor ? Her neyse, evet, derleyiciye 64 bit değerine sahip olduğunuzu söylemek moviçin yazmak mantıklı olacaktır "a"(1ULL)ve bu nedenle tüm kaydı doldurmak için genişletildiğinden emin olmalıdır. Pratikte yine de kullanacaktır mov $1, %eax, çünkü derleyicinin RAX = 0xff00000001veya başka bir şey bildiği çevreleyen kodun garip bir durumu yoksa, EAX yazmak sıfır olarak RAX'a uzanır . Çünkü lea, hala 32 bit işlenen boyutu kullanıyorsunuz, bu nedenle giriş kayıtlarındaki yüksek bitlerin 32 bit sonucu üzerinde hiçbir etkisi yoktur.
Peter Cordes

4

LEA: sadece bir "aritmetik" talimat ..

MOV işlenenler arasında veri aktarıyor ancak lea sadece hesaplıyor


LEA verileri açıkça taşır; bir hedef işlenen var. LEA her zaman hesaplamaz; kaynak işlenende ifade edilen etkin adresin hesaplanıp hesaplanmadığını hesaplar. LEA EAX, GLOBALVAR hesaplamaz; GLOBALVAR'ın adresini EAX'a taşır.
Kaz

@Kaz Geri bildiriminiz için teşekkür ederiz. kaynağım "LEA (etkin adres yükü) aslında aritmetik bir talimattır - gerçek bir bellek erişimi gerçekleştirmez, ancak adresleri hesaplamak için yaygın olarak kullanılır (bununla birlikte genel amaçlı tamsayıları hesaplayabilirsiniz)." form Eldad-Eilam kitap sayfası 149
muhasebeci

@Kaz: Bu yüzden adres zaten bir bağlantı-zaman sabiti olduğunda LEA gereksizdir; kullanın mov eax, offset GLOBALVAR. Sen edebilirsiniz LEA kullanmak, ancak biraz daha büyük kod boyutu var mov r32, imm32ve daha az bağlantı noktalarında çalışır hala adres hesaplama sürecinden geçer çünkü . lea reg, symbolPIC ve / veya düşük 32 bitin dışındaki adreslere ihtiyacınız olduğunda, yalnızca RIP'ye bağlı LEA için 64 bit olarak kullanışlıdır. 32 veya 16 bit kodda sıfır avantaj vardır. LEA, CPU'nun adresleme modlarını çözme / hesaplama yeteneğini ortaya koyan aritmetik bir talimattır.
Peter Cordes

@Kaz: aynı argümanla, imul eax, edx, 1bunun hesaplanmadığını söyleyebilirsiniz : sadece edx'i eax'a kopyalar. Ama aslında verilerinizi 3 döngü gecikmeli çarpandan geçirir. Ya da rorx eax, edx, 0sadece kopyalar (sıfıra döndür).
Peter Cordes

@PeterCordes Demek istediğim, hem LEA EAX, hem de GLOBALVAL ve MOV EAX, GLOBALVAR hemen bir işlenenden adresi alıyor. 1 çarpanı yok veya 0 ofseti uygulanıyor; donanım düzeyinde olabilir, ancak montaj dilinde veya talimat setinde görülmez.
Kaz

1

Çarpma, özel veya sıfır gibi durum bayrakları ayarlamak gibi tüm normal "hesaplama" talimatları, işareti. Karmaşık bir adres kullanırsanız AX xor:= mem[0x333 +BX + 8*CX] , bayraklar xor işlemine göre ayarlanır.

Şimdi adresi birden çok kez kullanmak isteyebilirsiniz. Böyle bir adresin bir kayıt defterine yüklenmesi hiçbir zaman durum bayraklarını ayarlamayı amaçlamaz ve neyse ki yapmaz. "Yükü etkin adres" ifadesi programcıyı bu konuda bilgilendirir. Tuhaf ifade buradan geliyor.

İşlemci, karmaşık adresi içeriğini işlemek için kullanabildiğinde, başka amaçlar için hesaplayabileceği açıktır. Gerçekten de, x <- 3*x+1bir talimatta bir dönüşüm gerçekleştirmek için kullanılabilir . Bu, montaj programlamasında genel bir kuraldır: Talimatları kullanın, ancak teknenizi sallar. Önemli olan tek şey, talimatın somutlaştırdığı dönüşümün sizin için yararlı olup olmadığıdır.

Sonuç olarak

MOV, X| T| AX'| R| BX|

ve

LEA, AX'| [BX]

AX üzerinde aynı etkiye sahiptir, ancak durum bayrakları üzerinde değil. (Bu ciasdis gösterimi.)


"Bu, montaj programlamasında genel bir kuraldır: Talimatları kullanın ancak teknenizi sallar." Şahsen bu tavsiyeyi, call lbl lbl: pop raxteknik olarak “çalışma” gibi şeylerden dolayı değerini elde etmenin bir yolu olarak vermem rip, ancak şube tahminini çok mutsuz edersiniz. Talimatları istediğiniz gibi kullanın, ancak zor bir şey yaparsanız ve bunun öngörmediğiniz sonuçları olursa
şaşırmayın

@ The6P4C Bu faydalı bir uyarıdır. Bununla birlikte, şube tahminini mutsuz hale getirmenin bir alternatifi yoksa, bunun için gitmek gerekir. Montaj programlamasında başka bir genel kural daha vardır. Bir şey yapmanın alternatif yolları olabilir ve alternatifler arasından akıllıca seçim yapmalısınız. BL kaydının içeriğini AL kaydına almanın yüzlerce yolu vardır. RAX'in geri kalanının korunması gerekmiyorsa LEA bir seçenek olabilir. Bayrakları etkilememek, binlerce x86 işlemci türünün bazılarında iyi bir fikir olabilir. Groetjes Albert
Albert van der Horst

-1

Birisi daha önce bahsetmişse beni affet, ancak bellek segmentasyonunun hala alakalı olduğu x86 günlerinde, bu iki talimattan aynı sonuçları alamayabilirsiniz:

LEA AX, DS:[0x1234]

ve

LEA AX, CS:[0x1234]

1
"Etkili adres" bir seg:offçiftin sadece "ofset" kısmıdır . LEA segment tabanından etkilenmez; her iki talimat da (verimsiz olarak) 0x1234AX'a aktarılacaktır. x86 maalesef bir kayıt veya kayıt çiftine tam bir doğrusal adres (etkili + segment tabanı) hesaplamanın kolay bir yolu yoktur.
Peter Cordes

@PeterCordes Çok faydalı, düzelttiğiniz için teşekkürler.
tzoz
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.