Đố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ó?
Đố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ó?
Câu trả lời:
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 i
là trong EAX
, và xcoord
và ycoord
là mỗi 32 bit (như vậy ycoord
là 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 y
ở EDX
. Hệ số tỷ lệ của 8 là bởi vì mỗi Point
kí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
.
mov
hướng dẫn và rời khỏi dấu ngoặc? MOV EDX, EBX + 8*EAX + 4
MOV
nguồ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ó.
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ớ.LEA
chấ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
ADD
không cung cấp:
- khả năng thực hiện phép cộng với hai hoặc ba toán hạng và
- 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.
Và LEA
khô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 + ECX
mà không ghi đè hoặc với kết quả.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 ]
và INC EAX
là cái sau thay đổi EFLAGS
nhưng cái trước thì không; Điều này bảo tồn CMP
nhà nước.
LEA EAX, [ EAX + EBX + 1234567 ]
tính tổng EAX
, EBX
và 1234567
(đó 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 ]
( 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 ]
và INC EAX
là cái sau thay đổi EFLAGS
nhưng cái trước thì không; điều này bảo tồn CMP
nhà nước
LEA
có 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)
Một tính năng quan trọng khác của LEA
hướng dẫn là nó không làm thay đổi các mã điều kiện như CF
và ZF
, trong khi tính toán địa chỉ bằng các hướng dẫn số học như ADD
hoặc MUL
khô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.
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 lea
khô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? )
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).
LEA
trê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" ...
LEA
cung 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.
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
LEA EAX, [EAX*3]
?
shl
hướ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 mul
hướng dẫn sử dụng thông thường, nó tự phụ và chậm hơn.
lea eax,[eax*3]
sẽ dịch để tương đương với lea eax,[eax+eax*2]
.
lea
là 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 ebx
con trỏ eax
hơ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ả.
Lý do lớn nhất mà bạn sử dụng LEA
qua a MOV
là 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 LEA
giống như một MOV
như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 * 8
và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 MOV
bị giới hạn trong việc thêm / bớt.
LEA
.
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ư.
Như các câu trả lời hiện có đã đề cập, LEA
có 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 LEA
và địa chỉ tham chiếu bộ nhớ khác), điều này có nghĩa là hoạt động số học trong LEA
và 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, MemoryAddress
việ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, LEA
có thể được sử dụng để chuyển đổi không PIC như PIC.
lea
trê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 add
hoặc sub
hoặ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 lea
trên một (phức tạp lea
) hoặc hai (đơn giản lea
). Quan trọng hơn, các lea
ALU 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ố.
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.
[esi]
hiếm khi rẻ hơn nói [esi + 4200]
và chỉ hiếm khi rẻ hơn [esi + ecx*8 + 4200]
.
[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 esi
giá trị ecx
nhâ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).
[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.
[esi]
và [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ỉ.
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
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ế MOV
bở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.
LEA
khô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 LEA
trê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 LEA
là thừa là sai lầm nghiêm trọng; dấu ngoặc không phải là LEA
cú 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 LEA
khô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à 8d
củaLEA
đã thay đổi thành 8b
.
Tất nhiên, LEA
mã 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.
Đâ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.
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
=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.
%%
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ý.
mov 4(%ebx, %eax, 8), %edx
không hợp lệ? Dù sao, vâng, mov
sẽ 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 = 0xff00000001
hoặ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.
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
mov eax, offset GLOBALVAR
thay 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, imm32
và 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, symbol
chỉ 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ỉ.
imul eax, edx, 1
khô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, 0
chỉ là bản sao (xoay bằng không).
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+1
trong 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|
và
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 .)
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