web-dev-qa-db-ja.com

LEA命令の目的は何ですか?

私にとっては、ファンキーなMOVのようです。その目的は何ですか?またいつ使用しますか?

603
user200557

他の人が指摘したように、LEA(load effective address)は特定の計算をするための「トリック」としてよく使われますが、それはその主な目的ではありません。 x86命令セットは、PascalやCなどの高水準言語をサポートするように設計されています。ここでは、配列、特に整数の配列または小さな構造体が一般的です。たとえば、(x、y)座標を表す構造体を考えます。

struct Point
{
     int xcoord;
     int ycoord;
};

では、次のような文を想像してください。

int y = points[i].ycoord;

points[]Pointの配列です。配列の基底が既にEBXにあり、変数iEAXにあり、xcoordycoordがそれぞれ32ビットであると仮定すると(したがってycoordは構造体のオフセット4バイトにあります)、

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

これはyEDXを上陸させます。 8の倍率は、各Pointのサイズが8バイトであるためです。それでは、 "address of"演算子&と同じ式を使ってみましょう。

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

この場合、ycoordの値は必要ありませんが、そのアドレスは必要です。それがLEA(実効アドレスのロード)が入ってくるところです。MOVの代わりに、コンパイラは次のものを生成することができます。

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

これはアドレスをESIにロードします。

721
I. J. Kennedy

"議会の禅" Abrash著:

LEA、メモリアドレッシング計算を実行するが実際にはメモリをアドレス指定しない唯一の命令。 LEAは標準的なメモリアドレッシングオペランドを受け入れますが、計算されたメモリオフセットを指定されたレジスタに格納することに他なりません。

それは私たちに何を与えるのでしょうか? ADDが提供しない2つのこと:

  1. 2つまたは3つのオペランドのいずれかで加算を実行する機能
  2. any registerに結果を格納する機能。ソースオペランドの1つだけではありません。

そしてLEAはフラグを変更しません。

  • LEA EAX, [ EAX + EBX + 1234567 ]EAX + EBX + 1234567を計算します(これは3つのオペランドです)。
  • LEA EAX, [ EBX + ECX ]は、結果をオーバーライドせずにEBX + ECXを計算します。
  • LEA EAX, [ EBX + N * EBX ]のように使用する場合は、定数による乗算(2、3、5、または9による乗算)(Nは1,2,4,8)。

LEA EAX, [ EAX + 1 ]INC EAXの違いは、後者はEFLAGSを変更しますが、前者は変更しないということです。これはCMP状態を維持します。

508
Frank Krueger

LEA命令のもう1つの重要な特徴は、CFZFのような算術命令でアドレスを計算しながら、ADDMULなどの条件コードを変更しないことです。この機能により、命令間の依存性のレベルが下がるため、コンパイラまたはハードウェアスケジューラによるさらなる最適化の余地があります。

94
Angus Lee

すべての説明にもかかわらず、LEAは算術演算です。

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

その名前がshift + add操作にとって非常に愚かだということです。その理由はすでにトップクラスの回答で説明されています(つまり、高レベルのメモリ参照を直接マッピングするように設計されています)。

78
hdante

たぶんLEA命令についてのもう一つのこと。 3、5、または9でレジスタを高速乗算するためにLEAを使用することもできます。

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

leaは「load実効アドレス」の略語です。ソースオペランドによるロケーション参照のアドレスをデスティネーションオペランドにロードします。たとえば、あなたはそれを使用することができます:

lea ebx, [ebx+eax*8]

1つの命令でebxポインタeax項目を(64ビット/要素の配列内で)さらに移動する。基本的に、ポインタを効率的に操作するために、x86アーキテクチャでサポートされている複雑なアドレッシングモードの恩恵を受けることができます。

55
Mehrdad Afshari

LEAに対してMOVを使用する最大の理由は、アドレスを計算するために使用しているレジスタに対して算術演算を実行する必要がある場合です。効果的には、 "free"のために効果的に組み合わせていくつかのレジスタでポインタ演算に相当する量を実行することができます。

それについて本当に混乱しているのは、あなたが通常LEAと同じようにMOVを書くのですが、実際にはメモリを間接参照していないということです。言い換えると:

MOV EAX, [ESP+4]

これはESP+4が指し示す内容をEAXに移動します。

LEA EAX, [EBX*8]

これは実効アドレスEBX * 8をEAXに移動させます、その場所にあるものではありません。ご覧のとおり、MOVは加減算に限定されていますが、2倍(スケーリング)で乗算することも可能です。

22
David Hoelzer

8086は、レジスタオペランドおよび有効アドレスを受け入れ、その有効アドレスのオフセット部分を計算するためにいくつかの計算を実行し、計算されたアドレスによって参照されるレジスタおよびメモリを含むいくつかの演算を実行する命令の大ファミリーを有する。その実際のメモリ操作をスキップすることを除いて、そのファミリの命令の1つを上記のように動作させることは非常に簡単でした。これ、指示:

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

内部的にはほとんど同じように実装されていました。違いはスキップされたステップです。どちらの指示も、次のように機能します。

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

インテルがこの命令を含める価値があると思った理由については、よくわかりませんが、実装するのが安価であるという事実が大きな要因となっていたでしょう。もう1つの要因は、IntelのアセンブラがBPレジスタに対してシンボルの定義を許可していたことです。 fnordがBP相対シンボル(BP + 8など)として定義されている場合、次のようになります。

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

BP相対アドレスにデータを格納するためにストーのようなものを使いたいとしたら、

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

より便利でした:

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

世界の「オフセット」を忘れると、値8ではなくlocation [BP + 8]の内容がDIに追加されることになります。おっとっと。

18
supercat

既存の回答で述べたように、LEAには、メモリにアクセスせずにメモリアドレッシング演算を実行し、単純な形式のadd命令ではなく、異なるレジスタに演算結果を保存するという利点があります。本当の根本的なパフォーマンス上の利点は、最新のプロセッサが効果的なアドレス生成(LEAおよびその他のメモリ参照アドレスを含む)のために別個のLEA ALUユニットとポートを持っていることです。これはLEA ALUの操作は、1つのコアで並行して実行できます。

LEAユニットの詳細については、Haswellアーキテクチャの次の記事を参照してください。 http://www.realworldtech.com/haswell-cpu/4/

他の回答で言及されていないもう1つの重要な点は、LEA REG, [MemoryAddress]命令がPIC(位置独立コード)であり、この命令のPC相対アドレスをエンコードしてMemoryAddressを参照することです。これは、相対仮想アドレスをエンコードし、最新のオペレーティングシステムでの再配置/パッチ適用が必要なMOV REG, MemoryAddressとは異なります(ASLRは一般的な機能です)。したがって、LEAを使用して、このような非PICをPICに変換できます。

10
Thomson

LEA命令は、CPUによる有効アドレスの時間のかかる計算を回避するために使用することができる。アドレスが繰り返し使用される場合は、使用されるたびに実効アドレスを計算するのではなく、アドレスをレジスタに格納する方が効果的です。

7
red-E

LEA(Load Effective Address)命令は、Intelプロセッサのメモリアドレッシングモードのいずれかから発生するアドレスを取得する方法です。

つまり、次のようにデータを移動するとします。

MOV EAX, <MEM-OPERAND>

指定されたメモリ位置の内容をターゲットレジスタに移動します。

MOVLEAに置き換えると、メモリ位置のアドレスは<MEM-OPERAND>アドレス式によってまったく同じ方法で計算されます。しかし、メモリロケーションの内容の代わりに、ロケーション自体を宛先に取得します。

LEAは特定の算術命令ではありません。これは、プロセッサのメモリアドレッシングモードのいずれかから発生する実効アドレスを傍受する方法です。

たとえば、単純な直接アドレスにLEAを使用できます。算術演算はまったく必要ありません。

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

これは有効です。 Linuxプロンプトでテストすることができます。

$ as
LEA 0, %eax
$ objdump -d a.out

a.out:     file format elf64-x86-64

Disassembly of section .text:

0000000000000000 <.text>:
   0:   8d 04 25 00 00 00 00    lea    0x0,%eax

ここでは、スケーリングされた値の加算もオフセットもありません。ゼロがEAXに移動しました。 MOVを即値オペランドで使用しても可能です。

これが、LEAの括弧が不必要であると考える人々がひどく誤っている理由です。大括弧はLEA構文ではありませんが、アドレッシングモードの一部です。

LEAはハードウェアレベルで本物です。生成された命令は実際のアドレッシングモードをエンコードし、プロセッサはそれをアドレスを計算するポイントまで実行します。その後、メモリ参照を生成するのではなく、そのアドレスを宛先に移動します。 (他の命令のアドレッシングモードのアドレス計算はCPUフラグには影響しないため、LEAはCPUフラグには影響しません。)

アドレス0から値をロードするのとは対照的に、

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

これは非常によく似たエンコーディングです。 LEA8dだけが8bに変わりました。

もちろん、このLEAエンコーディングは即値ゼロをEAXに移動するよりも長くなります。

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

短い選択肢があるからといって、LEAがこの可能性を排除する理由はありません。それはただ利用可能なアドレッシングモードと直交する方法で結合しています。

6
Kaz

これが一例です。

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

コンパイラオプションとして-O(最適化)を指定すると、gccは指定されたコード行のlea命令を見つけます。

6
user3634373

LEA:単なる "算術"命令です。

MOVはオペランド間でデータを転送しますが、leaは計算中です

4
the accountant

たくさんの答えがすでに完成しているようですが、同じ表現形式を使用した場合に、リーと移動の命令の動作が異なることを示すコード例をもう1つ追加します。

簡単に言えば、lea命令とmov命令の両方を、命令のsrcオペランドを囲む括弧とともに使用することができます。それらが () で囲まれている場合、 () 内の式は同じ方法で計算されます。ただし、2つの命令がsrcオペランドの計算値を異なる方法で解釈します。

式がleaとmovのどちらで使用されている場合でも、src値は次のように計算されます。

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

ただし、mov命令と共に使用すると、上記の式で生成されたアドレスが指す値にアクセスし、それを宛先に格納しようとします。

それに対して、lea命令が上記の式で実行されると、生成された値がそのままデスティネーションにロードされます。

以下のコードは、同じパラメータを使用してlea命令とmov命令を実行します。ただし、違いを理解するために、mov命令の結果として誤ったアドレスにアクセスすることによって発生するセグメンテーション違反を検出するためのユーザーレベルのシグナルハンドラを追加しました。

コード例

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

実行結果

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