Mục đích của hướng dẫn LEA là gì?


676

Đối với tôi, nó chỉ giống như một MOV vui nhộn. Mục đích của nó là gì và khi nào tôi nên sử dụng nó?


2
Xem thêm Sử dụng LEA trên các giá trị không có địa chỉ / con trỏ? : LEA chỉ là một hướng dẫn thay đổi và thêm. Có lẽ nó đã được thêm vào 8086 vì phần cứng đã có sẵn để giải mã và tính toán các chế độ địa chỉ, chứ không phải vì "dự định" chỉ để sử dụng với địa chỉ. Hãy nhớ rằng con trỏ chỉ là số nguyên trong lắp ráp.
Peter Cordes

Câu trả lời:


798

Như những người khác đã chỉ ra, LEA (tải địa chỉ hiệu quả) thường được sử dụng như một "mẹo" để thực hiện các tính toán nhất định, nhưng đó không phải là mục đích chính của nó. Tập lệnh x86 được thiết kế để hỗ trợ các ngôn ngữ cấp cao như Pascal và C, trong đó mảng mảng đặc biệt là mảng ints hoặc structs nhỏ. Ví dụ, hãy xem xét một cấu trúc đại diện cho tọa độ (x, y):

struct Point
{
     int xcoord;
     int ycoord;
};

Bây giờ hãy tưởng tượng một tuyên bố như:

int y = points[i].ycoord;

trong đó points[]là một mảng của Point. Giả sử các cơ sở của mảng là đã có trong EBX, và biến ilà trong EAX, và xcoordycoordlà mỗi 32 bit (như vậy ycoordlà tại offset 4 byte trong struct), tuyên bố này có thể được biên dịch để:

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

Mà sẽ hạ cánh yEDX. Hệ số tỷ lệ của 8 là bởi vì mỗi Pointkích thước là 8 byte. Bây giờ hãy xem xét cùng một biểu thức được sử dụng với toán tử "address of" &:

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

Trong trường hợp này, bạn không muốn giá trị của ycoord, nhưng địa chỉ của nó. Đó là nơi LEA(tải địa chỉ hiệu quả) đến. Thay vì a MOV, trình biên dịch có thể tạo

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

Nó sẽ tải địa chỉ trong ESI.


112
Nó sẽ không được sạch hơn để mở rộng movhướng dẫn và rời khỏi dấu ngoặc? MOV EDX, EBX + 8*EAX + 4
Natan Yellin

14
@imacake Bằng cách thay thế LEA bằng MOV chuyên dụng, bạn giữ sạch cú pháp: [] ngoặc luôn tương đương với việc hủy bỏ con trỏ trong C. Không có dấu ngoặc, bạn luôn tự xử lý con trỏ.
Natan Yellin

139
Làm toán theo hướng dẫn MOV (EBX + 8 * EAX + 4) không hợp lệ. LEA ESI, [EBX + 8 * EAX + 4] là hợp lệ vì đây là chế độ địa chỉ mà x86 hỗ trợ. vi.wikipedia.org/wiki/X86#Addressing_modes
Erik

29
@JonathanDickinson LEA giống như một MOVnguồn gián tiếp, ngoại trừ việc nó chỉ thực hiện theo yêu cầu và không phải là MOV. Nó không thực sự đọc từ địa chỉ được tính toán, chỉ cần tính toán nó.
hobbs

24
Erik, bình luận du lịch là không chính xác. MOV eax, [ebx + 8 * ecx + 4] là hợp lệ. Tuy nhiên MOV trả về nội dung của vị trí bộ nhớ thứ trong khi LEA trả về địa chỉ
Olorin

562

Từ "Zen of hội" của Abrash:

LEA, hướng dẫn duy nhất thực hiện tính toán địa chỉ bộ nhớ nhưng thực tế không giải quyết bộ nhớ. LEAchấp nhận toán hạng địa chỉ bộ nhớ tiêu chuẩn, nhưng không có gì khác hơn là lưu trữ phần bù bộ nhớ đã tính trong thanh ghi đã chỉ định, có thể là bất kỳ thanh ghi mục đích chung nào.

Điều đó mang lại cho chúng ta điều gì? Hai điều ADDkhông cung cấp:

  1. khả năng thực hiện phép cộng với hai hoặc ba toán hạng và
  2. khả năng lưu trữ kết quả trong bất kỳ đăng ký nào ; không chỉ là một trong các toán hạng nguồn.

LEAkhông thay đổi cờ.

Ví dụ

  • LEA EAX, [ EAX + EBX + 1234567 ]tính toán EAX + EBX + 1234567(đó là ba toán hạng)
  • LEA EAX, [ EBX + ECX ]tính toán EBX + ECXmà không ghi đè hoặc với kết quả.
  • nhân với hằng số (bằng hai, ba, năm hoặc chín), nếu bạn sử dụng nó như thế nào LEA EAX, [ EBX + N * EBX ](N có thể là 1,2,4,8).

Usecase khác là tiện dụng trong các vòng lặp: sự khác biệt giữa LEA EAX, [ EAX + 1 ]INC EAXlà cái sau thay đổi EFLAGSnhưng cái trước thì không; Điều này bảo tồn CMPnhà nước.


42
@AbidRahmanK một số ví dụ: LEA EAX, [ EAX + EBX + 1234567 ]tính tổng EAX, EBX1234567(đó là ba toán hạng). LEA EAX, [ EBX + ECX ]tính toán EBX + ECX mà không ghi đè hoặc với kết quả. Điều thứ ba LEAđược sử dụng cho (không được liệt kê bởi Frank) là nhân với hằng số (theo hai, ba, năm hoặc chín), nếu bạn sử dụng như thế LEA EAX, [ EBX + N * EBX ]( Ncó thể là 1,2,4,8). Usecase khác là tiện dụng trong các vòng lặp: sự khác biệt giữa LEA EAX, [ EAX + 1 ]INC EAXlà cái sau thay đổi EFLAGSnhưng cái trước thì không; điều này bảo tồn CMPnhà nước
FrankH.

@FrankH. Tôi vẫn không hiểu, vì vậy nó tải một con trỏ lên một nơi khác?

6
@ ripDaddy69 có, sắp xếp - nếu bằng "tải", bạn có nghĩa là "thực hiện tính toán địa chỉ / con trỏ mỹ phẩm". Nó không truy cập bộ nhớ (tức là không "dereference" con trỏ như được gọi trong thuật ngữ lập trình C).
FrankH.

2
+1: Điều này cho thấy rõ loại 'thủ thuật' nào LEAcó thể được sử dụng cho ... (xem "LEA (tải địa chỉ hiệu quả) thường được sử dụng như một" mẹo "để thực hiện các tính toán nhất định" trong câu trả lời phổ biến của IJ Kennedy ở trên)
Assad Ebrahim

3
Có một sự khác biệt lớn giữa 2 toán hạng LEA nhanh và 3 toán hạng LEA chậm. Hướng dẫn tối ưu hóa Intel cho biết đường dẫn nhanh LEA là chu kỳ đơn và đường dẫn chậm LEA mất ba chu kỳ. Ngoài ra, trên Skylake có hai đơn vị chức năng đường dẫn nhanh (cổng 1 và 5) và chỉ có một đơn vị chức năng đường dẫn chậm (cổng 1). Quy tắc mã hóa hội đồng / biên dịch 33 trong hướng dẫn thậm chí cảnh báo không sử dụng 3 toán hạng LEA.
Olsonist

110

Một tính năng quan trọng khác của LEAhướng dẫn là nó không làm thay đổi các mã điều kiện như CFZF, trong khi tính toán địa chỉ bằng các hướng dẫn số học như ADDhoặc MULkhông. Tính năng này làm giảm mức độ phụ thuộc giữa các hướng dẫn và do đó tạo khoảng trống để tối ưu hóa hơn nữa bởi trình biên dịch hoặc bộ lập lịch phần cứng.


1
Có, leađôi khi hữu ích cho trình biên dịch (hoặc bộ mã hóa con người) để làm toán mà không ghi lại kết quả cờ. Nhưng leakhông nhanh hơn add. Hầu hết các hướng dẫn x86 viết cờ. Việc triển khai x86 hiệu suất cao phải đổi tên EFLAGS hoặc tránh các nguy cơ ghi sau khi viết để mã thông thường chạy nhanh, do đó, các hướng dẫn tránh ghi cờ không tốt hơn vì điều đó. ( công cụ cờ một phần có thể tạo ra sự cố, xem hướng dẫn INC so với THÊM 1: Có vấn đề gì không? )
Peter Cordes

2
@PeterCordes: Ghét phải mang cái này lên đây nhưng - tôi có đơn độc khi nghĩ thẻ [x86-lea] mới này là dư thừa và không cần thiết không?
Michael Petch

2
@MichaelPetch: Vâng, tôi nghĩ nó quá cụ thể. Dường như có thể nhầm lẫn người mới bắt đầu không hiểu ngôn ngữ máy và mọi thứ (bao gồm cả con trỏ) chỉ là bit / byte / số nguyên, vì vậy có rất nhiều câu hỏi về nó với số lượng phiếu bầu khổng lồ. Nhưng có một thẻ cho nó ngụ ý rằng có chỗ cho một số câu hỏi mở trong tương lai, trong khi thực tế có khoảng 2 hoặc 3 tổng số không chỉ là trùng lặp. (nó là gì? Làm thế nào để sử dụng nó để nhân số nguyên? và cách nó chạy nội bộ trên AGU so với ALU và với độ trễ / thông lượng. Và có thể đó là mục đích "dự định")
Peter Cordes

@PeterCordes: Tôi đồng ý và nếu có bất kỳ điều gì thì tất cả các bài đăng này đang được chỉnh sửa đều là một bản sao của một vài câu hỏi liên quan đến LEA. Thay vì một thẻ, bất kỳ trùng lặp nên được xác định và đánh dấu imho.
Michael Petch

1
@EvanCarroll: hãy gắn thẻ tất cả các câu hỏi LEA, nếu bạn chưa hoàn thành. Như đã thảo luận ở trên, chúng tôi nghĩ rằng x86-cờ quá cụ thể cho một thẻ và không có nhiều phạm vi cho các câu hỏi không trùng lặp trong tương lai. Tôi nghĩ rằng sẽ thực sự rất nhiều công việc để thực sự chọn một câu hỏi và trả lời "tốt nhất" làm mục tiêu kép cho hầu hết trong số họ, hoặc thực sự quyết định nên chọn mod nào để hợp nhất.
Peter Cordes

93

Bất chấp mọi lời giải thích, LEA là một phép toán số học:

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

Chỉ là tên của nó cực kỳ ngu ngốc cho một ca làm việc + thêm hoạt động. Lý do cho điều đó đã được giải thích trong các câu trả lời được xếp hạng hàng đầu (nghĩa là nó được thiết kế để ánh xạ trực tiếp các tham chiếu bộ nhớ mức cao).


8
Và số học được thực hiện bởi phần cứng tính toán địa chỉ.
Ben Voigt

30
@BenVoigt Tôi đã từng nói rằng, bởi vì tôi là một người già :-) Theo truyền thống, CPU x86 đã sử dụng các đơn vị địa chỉ cho việc này, đã đồng ý. Nhưng "sự tách biệt" đã trở nên rất mờ nhạt trong những ngày này. Một số CPU không còn có AGU chuyên dụng nữa, một số khác đã chọn không thực thi LEAtrên AGU mà trên ALU số nguyên thông thường. Người ta phải đọc thông số kỹ thuật CPU rất chặt chẽ những ngày này để tìm ra "nơi công cụ chạy" ...
FrankH.

2
@FrankH.: CPU không theo thứ tự thường chạy LEA trên ALU, trong khi một số CPU theo thứ tự (như Atom) đôi khi chạy trên AGU (vì chúng không thể bận xử lý truy cập bộ nhớ).
Peter Cordes

3
Không, tên không ngu ngốc. LEAcung cấp cho bạn địa chỉ phát sinh từ bất kỳ chế độ địa chỉ liên quan đến bộ nhớ. Nó không phải là một sự thay đổi và thêm hoạt động.
Kaz

3
FWIW có rất ít (nếu có) CPU x86 hiện tại thực hiện thao tác trên AGU. Hầu hết hoặc tất cả chỉ sử dụng ALU như bất kỳ op số học nào khác.
BeeOnRope

77

Có lẽ chỉ là một điều khác về hướng dẫn LEA. Bạn cũng có thể sử dụng LEA để nhân nhanh các thanh ghi với 3, 5 hoặc 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

13
+1 cho mánh khóe. Nhưng tôi muốn hỏi một câu hỏi (có thể là ngu ngốc), tại sao không trực tiếp nhân với ba như thế này LEA EAX, [EAX*3]?
Abid Rahman K

13
@Abid Rahman K: Không có lệnh nào như tập lệnh CPU unde x86.
GJ.

50
@AbidRahmanK mặc dù cú pháp intel asm làm cho nó trông giống như một phép nhân, lệnh lệnh chỉ có thể mã hóa các hoạt động thay đổi. Opcode có 2 bit để mô tả sự thay đổi, do đó bạn chỉ có thể nhân với 1,2,4 hoặc 8.
ithkuil

6
@Koray Tugay: Bạn có thể sử dụng shift trái như shlhướng dẫn để nhân các thanh ghi với 2,4,8,16 ... nó nhanh hơn và ngắn hơn. Nhưng để nhân với các số khác nhau về sức mạnh của 2, chúng tôi sử dụng mulhướng dẫn sử dụng thông thường, nó tự phụ và chậm hơn.
GJ.

7
@GJ. Mặc dù không có mã hóa như vậy, một số nhà lắp ráp chấp nhận đây là một phím tắt, ví dụ như fasm. Vì vậy, ví dụ lea eax,[eax*3]sẽ dịch để tương đương với lea eax,[eax+eax*2].
Ruslan

59

lealà tên viết tắt của "tải địa chỉ hiệu quả". Nó tải địa chỉ của tham chiếu vị trí bằng toán hạng nguồn vào toán hạng đích. Chẳng hạn, bạn có thể sử dụng nó để:

lea ebx, [ebx+eax*8]

để di chuyển các mục ebxcon trỏ eaxhơn nữa (trong mảng 64 bit / phần tử) bằng một lệnh duy nhất. Về cơ bản, bạn được hưởng lợi từ các chế độ địa chỉ phức tạp được hỗ trợ bởi kiến ​​trúc x86 để thao tác con trỏ hiệu quả.


23

Lý do lớn nhất mà bạn sử dụng LEAqua a MOVlà nếu bạn cần thực hiện số học trên các thanh ghi mà bạn đang sử dụng để tính địa chỉ. Thực tế, bạn có thể thực hiện số tiền cho con số học con số trên một số thanh ghi kết hợp hiệu quả cho "miễn phí".

Điều thực sự khó hiểu về nó là bạn thường viết LEAgiống như một MOVnhưng bạn không thực sự làm mất trí nhớ. Nói cách khác:

MOV EAX, [ESP+4]

Điều này sẽ di chuyển nội dung của những gì ESP+4điểm vào EAX.

LEA EAX, [EBX*8]

Điều này sẽ chuyển địa chỉ hiệu quả EBX * 8vào EAX, chứ không phải những gì được tìm thấy ở vị trí đó. Như bạn có thể thấy, cũng có thể nhân với các thừa số của hai (tỷ lệ) trong khi a MOVbị giới hạn trong việc thêm / bớt.


Xin lỗi mọi người. @ big.love đánh lừa tôi bằng cách đưa ra câu trả lời cho điều này ba giờ trước, làm cho nó hiển thị là "mới" trong câu hỏi hội của tôi.
David Hoelzer

1
Tại sao cú pháp sử dụng dấu ngoặc khi nó không thực hiện địa chỉ bộ nhớ?
golopot

3
@ q4w56 Đây là một trong những điều mà câu trả lời là "Đó chỉ là cách bạn làm điều đó." Tôi tin rằng đó là một trong những lý do khiến mọi người gặp khó khăn như vậy để tìm ra điều gì LEA.
David Hoelzer

2
@ q4w56: đó là một sự thay đổi + thêm hướng dẫn sử dụng cú pháp toán hạng bộ nhớ mã hóa mã máy. Trên một số CPU, nó thậm chí có thể sử dụng phần cứng AGU, nhưng đó là một chi tiết lịch sử. Một thực tế vẫn có liên quan là phần cứng bộ giải mã đã tồn tại để giải mã loại shift + add này và LEA cho phép chúng ta sử dụng nó cho số học thay vì địa chỉ bộ nhớ. (Hoặc để tính toán địa chỉ nếu một đầu vào thực sự là một con trỏ).
Peter Cordes

20

8086 có một nhóm lớn các lệnh chấp nhận toán hạng đăng ký và địa chỉ hiệu quả, thực hiện một số tính toán để tính phần bù của địa chỉ hiệu dụng đó và thực hiện một số thao tác liên quan đến thanh ghi và bộ nhớ được gọi bởi địa chỉ được tính toán. Nó khá đơn giản để có một trong những hướng dẫn trong gia đình đó hành xử như trên ngoại trừ việc bỏ qua thao tác bộ nhớ thực tế đó. Đây, hướng dẫn:

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

đã được thực hiện gần như giống hệt nhau trong nội bộ. Sự khác biệt là một bước bỏ qua. Cả hai hướng dẫn đều hoạt động như:

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

Về lý do tại sao Intel nghĩ rằng hướng dẫn này có giá trị bao gồm, tôi không chắc chắn lắm, nhưng thực tế là nó rẻ để thực hiện sẽ là một yếu tố lớn. Một yếu tố khác có lẽ là trình biên dịch chương trình của Intel cho phép các ký hiệu được xác định liên quan đến thanh ghi BP. Nếu fnordđược định nghĩa là ký hiệu liên quan đến HA (ví dụ: BP + 8), người ta có thể nói:

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

Nếu một người muốn sử dụng một cái gì đó như stosw để lưu trữ dữ liệu đến một địa chỉ liên quan đến BP, có thể nói

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

thuận tiện hơn:

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

Lưu ý rằng việc quên "thế giới" sẽ khiến nội dung của vị trí [BP + 8], thay vì giá trị 8, được thêm vào DI. Giáo sư.


12

Như các câu trả lời hiện có đã đề cập, LEAcó các ưu điểm của việc thực hiện bộ nhớ địa chỉ số học mà không cần truy cập bộ nhớ, lưu kết quả số học vào một thanh ghi khác thay vì hình thức thêm lệnh đơn giản. Lợi ích hiệu năng cơ bản thực sự là bộ xử lý hiện đại có một đơn vị và cổng LEA ALU riêng để tạo địa chỉ hiệu quả (bao gồm LEAvà địa chỉ tham chiếu bộ nhớ khác), điều này có nghĩa là hoạt động số học trong LEAvà hoạt động số học bình thường khác trong ALU có thể được thực hiện song song trong một cốt lõi.

Kiểm tra bài viết này về kiến ​​trúc Haswell để biết một số chi tiết về đơn vị LEA: http://www.realworldtech.com/haswell-cpu/4/

Một điểm quan trọng khác không được đề cập trong các câu trả lời khác là LEA REG, [MemoryAddress]hướng dẫn là PIC (mã độc lập vị trí) mã hóa địa chỉ tương đối của PC trong hướng dẫn này để tham chiếu MemoryAddress. Điều này khác với MOV REG, MemoryAddressviệc mã hóa địa chỉ ảo tương đối và yêu cầu di chuyển / vá lỗi trong các hệ điều hành hiện đại (như ASLR là tính năng phổ biến). Vì vậy, LEAcó thể được sử dụng để chuyển đổi không PIC như PIC.


2
Phần "LEA ALU riêng biệt" hầu hết là không đúng sự thật. Các CPU hiện đại thực thi leatrên một hoặc nhiều ALU tương tự thực thi các lệnh số học khác (nhưng nhìn chung chúng ít hơn so với các số học khác). Chẳng hạn, CPU Haswell được đề cập có thể thực thi addhoặc subhoặc hầu hết các hoạt động số học cơ bản khác trên bốn ALU khác nhau , nhưng chỉ có thể thực thi leatrên một (phức tạp lea) hoặc hai (đơn giản lea). Quan trọng hơn, các leaALU hai tài khoản đó chỉ đơn giản là hai trong số bốn ALU có thể thực hiện các hướng dẫn khác, do đó không có lợi ích song song như đã tuyên bố.
BeeOnRope

Bài viết bạn liên kết (chính xác) cho thấy LEA nằm trên cùng một cổng với ALU số nguyên (add / sub / boolean) và đơn vị MUL số nguyên trong Haswell. (Và ALU vector bao gồm FP ADD / MUL / FMA). Đơn vị LEA chỉ đơn giản là trên cổng 5, cũng chạy ADD / SUB / bất cứ thứ gì, và xáo trộn vector và các thứ khác. Lý do duy nhất tôi không hạ thấp là bạn chỉ ra việc sử dụng LEA tương đối RIP (chỉ dành cho x86-64).
Peter Cordes

8

Lệnh LEA có thể được sử dụng để tránh tính toán tốn thời gian của các địa chỉ hiệu quả của CPU. Nếu một địa chỉ được sử dụng nhiều lần, việc lưu trữ nó trong một thanh ghi sẽ hiệu quả hơn thay vì tính toán địa chỉ hiệu quả mỗi khi nó được sử dụng.


Không nhất thiết phải trên x86 hiện đại. Hầu hết các chế độ địa chỉ có cùng chi phí, với một số cảnh báo. Vì vậy, [esi]hiếm khi rẻ hơn nói [esi + 4200]và chỉ hiếm khi rẻ hơn [esi + ecx*8 + 4200].
BeeOnRope

@BeeOnRope [esi]không rẻ hơn [esi + ecx*8 + 4200]. Nhưng tại sao phải so sánh? Chúng không tương đương. Nếu bạn muốn cái trước chỉ định cùng một vị trí bộ nhớ với cái sau, bạn cần có hướng dẫn bổ sung: bạn phải thêm vào esigiá trị ecxnhân với 8. Uh oh, phép nhân sẽ làm tắc nghẽn cờ CPU của bạn! Sau đó, bạn phải thêm 4200. Các hướng dẫn bổ sung này thêm vào kích thước mã (chiếm không gian trong bộ đệm hướng dẫn, chu kỳ để tìm nạp).
Kaz

2
@Kaz - Tôi nghĩ bạn đã bỏ lỡ quan điểm của tôi (nếu không tôi đã bỏ lỡ quan điểm của OP). Tôi hiểu rằng OP đang nói rằng nếu bạn sẽ sử dụng một cái gì đó như [esi + 4200]lặp đi lặp lại trong một chuỗi các hướng dẫn, thì tốt hơn hết là trước tiên tải địa chỉ hiệu quả vào một thanh ghi và sử dụng nó. Ví dụ, thay vì viết add eax, [esi + 4200]; add ebx, [esi + 4200]; add ecx, [esi + 4200], bạn nên thích lea edi, [esi + 4200]; add eax, [edi]; add ebx, [edi]; add ecx, [edi], điều này hiếm khi nhanh hơn. Ít nhất đó là cách giải thích đơn giản cho câu trả lời này.
BeeOnRope

Vì vậy, lý do tôi đã so sánh [esi][esi + 4200](hoặc [esi + ecx*8 + 4200]đó là sự đơn giản hóa mà OP đang đề xuất (theo tôi hiểu): rằng các lệnh N có cùng địa chỉ phức tạp được chuyển thành N hướng dẫn với địa chỉ đơn giản (một reg), cộng với một lea, vì việc xử lý địa chỉ phức tạp là "tốn thời gian". Trên thực tế, tốc độ chậm hơn ngay cả trên x86 hiện đại, nhưng chỉ có độ trễ có vẻ không quan trọng đối với các hướng dẫn liên tiếp có cùng địa chỉ.
BeeOnRope

1
Có lẽ bạn giảm bớt một số áp lực đăng ký, vâng - nhưng điều ngược lại có thể là: nếu các thanh ghi bạn tạo địa chỉ hiệu quả đang hoạt động, bạn cần một thanh ghi khác để lưu kết quả leađể nó tăng áp lực trong trường hợp đó. Nói chung, lưu trữ trung gian là một nguyên nhân của áp lực đăng ký, không phải là một giải pháp cho nó - nhưng tôi nghĩ trong hầu hết các tình huống đó là một rửa. @Kaz
BeeOnRope

7

Hướng dẫn LEA (Tải địa chỉ hiệu quả) là cách lấy địa chỉ phát sinh từ bất kỳ chế độ địa chỉ bộ nhớ nào của bộ xử lý Intel.

Điều đó có nghĩa là, nếu chúng ta có một dữ liệu di chuyển như thế này:

MOV EAX, <MEM-OPERAND>

nó di chuyển nội dung của vị trí bộ nhớ được chỉ định vào thanh ghi đích.

Nếu chúng ta thay thế MOVbởi LEA, thì địa chỉ của vị trí bộ nhớ được tính theo cùng một cách chính xác bằng <MEM-OPERAND>biểu thức địa chỉ. Nhưng thay vì nội dung của vị trí bộ nhớ, chúng ta có được vị trí đó vào đích.

LEAkhông phải là một hướng dẫn số học cụ thể; đó là một cách chặn địa chỉ hiệu quả phát sinh từ bất kỳ một trong các chế độ địa chỉ bộ nhớ của bộ xử lý.

Chẳng hạn, chúng ta có thể sử dụng LEAtrên một địa chỉ trực tiếp đơn giản. Không có số học nào được tham gia cả:

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

Điều này là hợp lệ; chúng ta có thể kiểm tra nó tại dấu nhắc của 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

Ở đây, không có thêm giá trị tỷ lệ và không có giá trị bù. Số không được chuyển vào EAX. Chúng tôi có thể làm điều đó bằng cách sử dụng MOV với toán hạng ngay lập tức.

Đây là lý do tại sao những người nghĩ rằng dấu ngoặc trong LEAlà thừa là sai lầm nghiêm trọng; dấu ngoặc không phải là LEAcú pháp mà là một phần của chế độ địa chỉ.

LEA là có thật ở cấp độ phần cứng. Lệnh được tạo sẽ mã hóa chế độ địa chỉ thực tế và bộ xử lý mang nó đến điểm tính toán địa chỉ. Sau đó, nó di chuyển địa chỉ đó đến đích thay vì tạo tham chiếu bộ nhớ. (Vì việc tính toán địa chỉ của chế độ địa chỉ trong bất kỳ lệnh nào khác không ảnh hưởng đến cờ CPU, nên LEAkhông ảnh hưởng đến cờ CPU.)

Tương phản với việc tải giá trị từ địa chỉ 0:

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

Đó là một mã hóa rất giống nhau, thấy không? Chỉ là 8dcủaLEA đã thay đổi thành 8b.

Tất nhiên, LEAmã hóa này dài hơn việc chuyển số 0 ngay lập tức vào EAX:

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

Không có lý do LEAđể loại trừ khả năng này mặc dù chỉ vì có một sự thay thế ngắn hơn; nó chỉ kết hợp theo cách trực giao với các chế độ địa chỉ có sẵn.


6

Đây là một ví dụ.

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

Với tùy chọn -O (tối ưu hóa) làm trình biên dịch, gcc sẽ tìm thấy lệnh khởi động cho dòng mã được chỉ định.


6

Có vẻ như rất nhiều câu trả lời đã hoàn tất, tôi muốn thêm một mã ví dụ nữa để hiển thị cách thức lệnh và di chuyển hoạt động khác nhau khi chúng có cùng định dạng biểu thức.

Để làm cho một câu chuyện dài ngắn, cả hai lệnh hướng dẫn và lệnh Mov đều có thể được sử dụng với dấu ngoặc đơn kèm theo toán hạng src của hướng dẫn. Khi chúng được đặt cùng với () , biểu thức trong () được tính theo cùng một cách; tuy nhiên, hai hướng dẫn sẽ diễn giải giá trị được tính toán trong toán hạng src theo một cách khác.

Cho dù biểu thức được sử dụng với cờ hoặc Mov, giá trị src được tính như dưới đây.

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

Tuy nhiên, khi nó được sử dụng với lệnh Mov, nó cố gắng truy cập giá trị được trỏ đến bởi địa chỉ được tạo bởi biểu thức trên và lưu trữ nó đến đích.

Ngược lại, khi lệnh lệnh được thực thi với biểu thức trên, nó sẽ tải giá trị được tạo như đích đến.

Đoạn mã dưới đây thực hiện lệnh lệnh và lệnh Mov với cùng một tham số. Tuy nhiên, để nắm bắt sự khác biệt, tôi đã thêm một trình xử lý tín hiệu cấp người dùng để bắt lỗi phân đoạn gây ra bằng cách truy cập một địa chỉ sai do kết quả của lệnh Mov.

Mã ví dụ

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

Kết quả thực hiện

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

1
Việc chia nhỏ mã asm nội tuyến của bạn thành các câu lệnh riêng biệt là không an toàn và danh sách ghi đè của bạn không đầy đủ. Khối asm cơ bản cho trình biên dịch không có clobbers, nhưng nó thực sự sửa đổi một số thanh ghi. Ngoài ra, bạn có thể sử dụng =dđể báo cho trình biên dịch kết quả là trong EDX, lưu a mov. Bạn cũng để lại một tuyên bố clobber sớm trên đầu ra. Điều này không thể hiện những gì bạn đang cố gắng chứng minh, nhưng cũng là một ví dụ tồi tệ gây hiểu lầm về mã asm nội tuyến sẽ bị phá vỡ nếu được sử dụng trong các bối cảnh khác. Đó là một điều xấu cho một câu trả lời tràn stack.
Peter Cordes

Nếu bạn không muốn viết %%trên tất cả các tên đăng ký trong Extended asm, thì hãy sử dụng các ràng buộc đầu vào. như asm("lea 4(%%ebx, %%eax, 8), %%edx" : "=d"(result_of_lea) : "a"(1), "b"(2));. Để các thanh ghi init của trình biên dịch có nghĩa là bạn cũng không phải khai báo clobbers. Bạn đang quá phức tạp mọi thứ bằng cách xor-zeroing trước khi Mov-ngay lập tức ghi đè lên toàn bộ đăng ký.
Peter Cordes

@PeterCordes Cảm ơn, Peter, bạn có muốn tôi xóa câu trả lời này hoặc sửa đổi nó theo ý kiến ​​của bạn không?
Jaehyuk Lee

1
Nếu bạn sửa mã asm nội tuyến, nó sẽ không gây hại gì và có thể là một ví dụ cụ thể tốt cho những người mới bắt đầu không hiểu các câu trả lời khác. Không cần phải xóa, và đó là một sửa chữa dễ dàng như tôi đã thể hiện trong bình luận cuối cùng của mình. Tôi nghĩ rằng nó sẽ có giá trị upvote nếu ví dụ xấu về asm nội tuyến được sửa thành một ví dụ "tốt". (Tôi không downvote)
Peter Cordes

1
Trường hợp có ai nói rằng mov 4(%ebx, %eax, 8), %edxkhông hợp lệ? Dù sao, vâng, movsẽ rất hợp lý khi viết "a"(1ULL)để báo cho trình biên dịch rằng bạn có giá trị 64 bit, và do đó, nó cần đảm bảo rằng nó được mở rộng để điền vào toàn bộ thanh ghi. Trong thực tế, nó vẫn sẽ sử dụng mov $1, %eax, bởi vì viết EAX zero-extends vào RAX, trừ khi bạn có một tình huống kỳ lạ về mã xung quanh nơi trình biên dịch biết rằng RAX = 0xff00000001hoặc một cái gì đó. Đối với lea, bạn vẫn đang sử dụng kích thước toán hạng 32 bit, do đó, bất kỳ bit cao đi lạc nào trong các thanh ghi đầu vào đều không ảnh hưởng đến kết quả 32 bit.
Peter Cordes

4

LEA: chỉ là một hướng dẫn "số học" ..

MOV chuyển dữ liệu giữa các toán hạng nhưng chỉ là tính toán


LEA rõ ràng di chuyển dữ liệu; nó có một toán hạng đích. LEA không phải lúc nào cũng tính toán; nó tính toán nếu địa chỉ hiệu quả được biểu thị trong toán hạng nguồn tính toán. LEA EAX, GLOBALVAR không tính toán; nó chỉ chuyển địa chỉ của GLOBALVAR vào EAX.
Kaz

@Kaz cảm ơn phản hồi của bạn. nguồn của tôi là "LEA (tải địa chỉ hiệu quả) về cơ bản là một lệnh số học. Nó không thực hiện bất kỳ truy cập bộ nhớ thực tế nào, nhưng thường được sử dụng để tính toán địa chỉ (mặc dù bạn có thể tính các số nguyên cho mục đích chung với nó)." mẫu sách Eldad-Eilam trang 149
kế toán

@Kaz: Đó là lý do LEA dư thừa khi địa chỉ đã là hằng số thời gian liên kết; sử dụng mov eax, offset GLOBALVARthay thế. Bạn có thể sử dụng LEA, nhưng kích thước mã lớn hơn một chút so với mov r32, imm32và chạy trên ít cổng hơn, vì nó vẫn trải qua quá trình tính toán địa chỉ . lea reg, symbolchỉ hữu ích trong 64 bit cho LEA tương đối RIP, khi bạn cần PIC và / hoặc địa chỉ bên ngoài 32 bit thấp. Trong mã 32 hoặc 16 bit, không có lợi thế. LEA là một hướng dẫn số học cho thấy khả năng của CPU để giải mã / tính toán các chế độ địa chỉ.
Peter Cordes

@Kaz: bằng cách lập luận tương tự, bạn có thể nói rằng imul eax, edx, 1không tính toán: nó chỉ sao chép edx sang eax. Nhưng thực tế nó chạy dữ liệu của bạn thông qua hệ số nhân với độ trễ 3 chu kỳ. Hoặc đó rorx eax, edx, 0chỉ là bản sao (xoay bằng không).
Peter Cordes

@PeterCordes Quan điểm của tôi là cả LEA EAX, GLOBALVAL và MOV EAX, GLOBALVAR chỉ cần lấy địa chỉ từ một toán hạng ngay lập tức. Không có hệ số nhân của 1, hoặc bù 0 được áp dụng; nó có thể là như vậy ở cấp độ phần cứng nhưng nó không được nhìn thấy trong ngôn ngữ lắp ráp hoặc tập lệnh.
Kaz

1

Tất cả các hướng dẫn "tính toán" thông thường như thêm phép nhân, độc quyền hoặc đặt các cờ trạng thái như 0, ký. Nếu bạn sử dụng một địa chỉ phức tạp,AX xor:= mem[0x333 +BX + 8*CX] các cờ được đặt theo thao tác xor.

Bây giờ bạn có thể muốn sử dụng địa chỉ nhiều lần. Tải một địa chỉ như vậy vào một thanh ghi không bao giờ có ý định đặt cờ trạng thái và may mắn là không. Cụm từ "tải địa chỉ hiệu quả" làm cho lập trình viên nhận thức được điều đó. Đó là nơi biểu hiện kỳ ​​lạ đến từ.

Rõ ràng là một khi bộ xử lý có khả năng sử dụng địa chỉ phức tạp để xử lý nội dung của nó, nó có khả năng tính toán cho các mục đích khác. Thật vậy, nó có thể được sử dụng để thực hiện chuyển đổi x <- 3*x+1trong một lệnh. Đây là một quy tắc chung trong lập trình lắp ráp: Sử dụng các hướng dẫn tuy nhiên nó làm rung chuyển thuyền của bạn. Điều duy nhất quan trọng là liệu phép biến đổi cụ thể được thể hiện bởi hướng dẫn có hữu ích cho bạn hay không.

Dòng dưới cùng

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

LEA, AX'| [BX]

có tác dụng tương tự đối với AX nhưng không phải trên các cờ trạng thái. (Đây là ký hiệu ciasdis .)


"Đây là một quy tắc chung trong lập trình lắp ráp: Sử dụng các hướng dẫn tuy nhiên nó làm rung chuyển thuyền của bạn." Cá nhân tôi sẽ không đưa ra lời khuyên đó, vì những điều như call lbl lbl: pop rax"làm việc" về mặt kỹ thuật như một cách để có được giá trị rip, nhưng bạn sẽ đưa ra dự đoán chi nhánh rất không vui. Sử dụng các hướng dẫn theo cách bạn muốn, nhưng đừng ngạc nhiên nếu bạn làm điều gì đó khó khăn và nó có hậu quả mà bạn không lường trước được
The6P4C

@ The6P4C Đó là một cảnh báo hữu ích. Tuy nhiên, nếu không có cách nào khác để làm cho dự đoán chi nhánh không hài lòng, người ta phải thực hiện nó. Có một quy tắc chung khác trong lập trình lắp ráp. Có thể có những cách khác để làm một cái gì đó và bạn phải chọn một cách khôn ngoan từ các lựa chọn thay thế. Có hàng trăm cách để đưa nội dung đăng ký BL vào đăng ký AL. Nếu phần còn lại của RAX không cần được bảo tồn, LEA có thể là một lựa chọn. Không ảnh hưởng đến các cờ có thể là một ý tưởng tốt trên một số trong hàng ngàn loại bộ xử lý x86. Groetjes Albert
Albert van der Horst
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.