LEA 교육의 목적은 무엇입니까?


676

나를 위해, 그것은 펑키 MOV처럼 보인다. 그 목적은 무엇이며 언제 사용해야합니까?


2
주소 / 포인터가 아닌 값에 LEA 사용을 참조하십시오 . : LEA는 단지 시프트 및 추가 명령어입니다. 하드웨어가 이미 주소와 함께 사용하기위한 "의도 된"것이 아니라 주소 지정 모드를 디코딩하고 계산하기 위해 이미 있기 때문에 8086에 추가되었을 수 있습니다. 포인터는 어셈블리에서 정수일뿐입니다.
Peter Cordes

답변:


798

다른 사람들이 지적했듯이 LEA (부하 유효 주소)는 종종 특정 계산을 수행하기위한 "트릭"으로 사용되지만 이는 기본 목적이 아닙니다. x86 명령어 세트는 Pascal 및 C와 같은 고급 언어를 지원하도록 설계되었으며, 특히 배열 int 또는 작은 구조체 배열이 일반적입니다. 예를 들어 (x, y) 좌표를 나타내는 구조체를 고려하십시오.

struct Point
{
     int xcoord;
     int ycoord;
};

이제 다음과 같은 진술을 상상해보십시오.

int y = points[i].ycoord;

points[]의 배열은 어디 입니까 Point? 어레이의 기본 가정은 이미 EBX한 변수 i이다 EAX하고 xcoord그리고 ycoord각각 32 비트 (너무되어 ycoord구조체 오프셋 4 바이트로된다),이 문에 컴파일 될 수있다 :

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

이는 토지됩니다 yEDX. 8의 스케일 팩터는 각각 Point크기가 8 바이트 이기 때문 입니다. 이제 "주소"연산자와 함께 사용 된 것과 동일한 표현을 고려하십시오 :

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

이 경우의 값이 ycoord아니라 주소를 원합니다 . 곳이다 LEA(부하 효과적인 주소)에서 제공됩니다. 대신의 MOV컴파일러는 생성 할 수 있습니다

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

에 주소를로드합니다 ESI.


112
mov명령 을 확장 하고 브래킷을 제거하는 것이 더 깨끗하지 않았 습니까? MOV EDX, EBX + 8*EAX + 4
Natan Yellin

14
@imacake LEA를 특수한 MOV로 대체하면 구문을 깔끔하게 유지할 수 있습니다. [] 괄호는 항상 C에서 포인터를 역 참조하는 것과 같습니다. 괄호가 없으면 항상 포인터 자체를 처리합니다.
Natan Yellin

139
MOV 명령어 (EBX + 8 * EAX + 4)에서 수학을 수행하는 것은 유효하지 않습니다. LEA ESI, [EBX + 8 * EAX + 4]는 x86이 지원하는 주소 지정 모드이므로 유효합니다. en.wikipedia.org/wiki/X86#Addressing_modes
Erik

29
@JonathanDickinson LEA는 MOV간접 소스 만있는 것과 비슷하지만 간접적으로 만 수행하고 MOV. 실제로 계산 된 주소 에서 읽지 않고 단지 계산합니다.
hobbs

24
에릭, 여행 코멘트가 정확하지 않습니다. MOV eax, [ebx + 8 * ecx + 4]가 유효합니다. 그러나 MOV는 메모리 위치의 내용을 반환하지만 LEA는 주소를 반환합니다
Olorin

562

로부터 "총회의 선" Abrash의 :

LEA메모리 어드레싱 계산을 수행하지만 실제로 메모리를 어드레싱하지는 않는 유일한 명령어입니다. LEA표준 메모리 주소 지정 피연산자를 허용하지만 계산 된 메모리 오프셋을 지정된 레지스터에 저장하는 것 외에는 범용 레지스터 일 수 있습니다.

그것은 우리에게 무엇을 제공합니까? ADD제공하지 않는 두 가지 :

  1. 2 개 또는 3 개의 피연산자로 덧셈을 수행하는 기능
  2. 결과를 임의의 레지스터 에 저장하는 능력 ; 소스 피연산자 중 하나만이 아닙니다.

그리고 LEA플래그를 변경하지 않습니다.

  • LEA EAX, [ EAX + EBX + 1234567 ]계산 EAX + EBX + 1234567(세 피연산자)
  • LEA EAX, [ EBX + ECX ]결과 EBX + ECX를 무시하지 않고 계산 합니다.
  • LEA EAX, [ EBX + N * EBX ](N은 1,2,4,8이 될 수 있음) 처럼 사용하면 상수로 곱하기 (2, 3, 5 또는 9 ).

다른 유스 케이스는 루프에 편리합니다 : 차이 LEA EAX, [ EAX + 1 ]와는 INC EAX그 후자의 변화입니다 EFLAGS하지만 전자는하지 않습니다; 이것은 CMP상태를 유지 합니다.


42
: 일부 예 @AbidRahmanK LEA EAX, [ EAX + EBX + 1234567 ]의 합을 계산 EAX, EBX1234567(즉, 세 개의 오퍼랜드이다). LEA EAX, [ EBX + ECX ]결과 EBX + ECX 무시 하지 않고 계산 합니다. 세 번째 LEA로 (Frank에 나열되지 않음 ) 사용하는 경우 ( 1,2,4,8 일 수 있음) 상수를 사용하여 곱하기 (2, 3, 5 또는 9 )입니다. 다른 유스 케이스는 루프에 편리합니다 : 차이 와는 그 후자의 변화입니다 하지만 전자는하지 않습니다; 이것은 상태를 유지 합니다LEA EAX, [ EBX + N * EBX ]NLEA EAX, [ EAX + 1 ]INC EAXEFLAGSCMP
FrankH.

@ 프랭크 나는 여전히 이해하지 못하므로 다른 곳에 포인터를로드합니까?

6
@ ripDaddy69 예, 일종의- "로드"에 의해 "주소 계산 / 포인터 산술 수행"을 의미합니다. 메모리에 액세스 하지 않습니다 (즉, C 프로그래밍 용어에서 호출 될 때 포인터를 "역 참조"하지 않음).
FrankH.

2
+1 : 이것은 무엇을 '트릭'의 종류를 명시한다 LEA( ") LEA (부하 효과적인 주소 종종 사용"을 참조하십시오 위의 IJ 케네디의 인기 대답은 "특정 계산을 할 수있는"트릭)에 대한 ... 사용할 수 있습니다
아사드을 Ebrahim

3
빠른 2 개의 피연산자 LEA와 느린 3 개의 피연산자 LEA 사이에는 큰 차이가 있습니다. 인텔 최적화 매뉴얼에 따르면 빠른 경로 LEA는 단일주기이고 느린 경로 LEA는 3주기가 필요합니다. 또한 Skylake에는 두 개의 빠른 경로 기능 단위 (포트 1 및 5)가 있으며 하나의 느린 경로 기능 단위 (포트 1) 만 있습니다. 매뉴얼의 어셈블리 / 컴파일러 코딩 규칙 33은 3 개의 피연산자 LEA 사용에 대해 경고합니다.
Olsonist

110

의 또 다른 중요한 기능 LEA명령은 같은 조건 코드를 변경하지 않는다는 것입니다 CFZF같은 산술 명령어로 주소를 계산하는 동안, ADD또는 MUL한다. 이 기능은 명령어 간의 종속성 수준을 낮추므로 컴파일러 또는 하드웨어 스케줄러에 의해 추가 최적화를위한 공간을 만듭니다.


1
예, lea때로는 컴파일러 (또는 인간 코더)가 플래그 결과를 방해하지 않고 수학을 수행하는 데 유용합니다. 그러나 lea보다 빠르지 않습니다 add. 대부분의 x86 명령어는 플래그를 작성합니다. 고성능 x86 구현은 EFLAGS의 이름을 바꾸거나 일반 코드가 빠르게 실행될 수 있는 쓰기 후 쓰기 위험 을 피해야합니다. 따라서 플래그 쓰기를 피하는 명령어는 그로 인해 좋지 않습니다. ( 부분 플래그 문제는 문제를 일으킬 수 있습니다. INC 명령 vs ADD 1 : 문제가됩니까? )
Peter Cordes

2
@PeterCordes : 이것을 여기에 가져 오는 것을 싫어하지만-이 새로운 [x86-lea] 태그가 중복되고 불필요하다고 생각하는 것만으로도 혼자입니까?
마이클 페치

2
@ MichaelPetch : 예, 너무 구체적이라고 생각합니다. 기계 언어를 이해하지 못하는 초보자와 혼동하는 것 (포인터 포함)은 단지 비트 / 바이트 / 정수이므로 많은 투표로 그것에 대한 많은 질문이 있습니다. 그러나 태그가 있으면 실제로 중복되지 않는 약 2 ~ 3 개의 합계가있을 때 미래의 개방형 질문 수에 대한 여지가 있음을 의미합니다. (무엇입니까? 정수를 곱하는 데 사용하는 방법? 그리고 AGU와 ALU에서 내부적으로 실행되는 방식 및 대기 시간 / 처리량에 따라 다릅니다. 그리고 아마도 "의도 된"목적 일 수도 있습니다)
Peter Cordes

@PeterCordes : 동의합니다. 편집중인 모든 게시물이 종료되는 LEA 관련 질문 중 일부와 거의 동일합니다. 태그 대신 중복을 식별하고 imho로 표시해야합니다.
Michael Petch

1
@EvanCarroll : 아직 완료하지 않은 경우 모든 LEA 질문에 태그를 지정하십시오. 위에서 논의한 바와 같이, 우리는 x86-lea 가 태그에 대해 너무 구체적 이라고 생각 하며, 중복되지 않는 향후 질문에 대한 범위는 많지 않습니다. 하지만 실제로 "최고의"Q & A를 대부분의 대상으로 선택하거나 실제로 어떤 모드를 병합할지 결정하는 것이 많은 작업이라고 생각합니다 .
Peter Cordes

93

모든 설명에도 불구하고 LEA는 산술 연산입니다.

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

단지 shift + add 연산의 이름이 멍청하다는 것입니다. 그 이유는 이미 최고 등급의 답변에서 설명되었습니다 (즉, 높은 수준의 메모리 참조를 직접 매핑하도록 설계되었습니다).


8
그리고 계산은 주소 계산 하드웨어에 의해 수행됩니다.
벤 Voigt

30
@BenVoigt 나는 오래된 녀석이기 때문에 말했었습니다 :-) 전통적으로 x86 CPU는 주소 지정 단위를 사용했습니다. 그러나 "분리"는 요즘 매우 모호해졌습니다. 일부 CPU에는 더 이상 전용 AGU 가없고 다른 CPU는 AGU에서 실행하지 않고 LEA일반 정수 ALU에서 실행하도록 선택했습니다 . 요즘 CPU 사양을 아주 자세히
읽어서

2
@FrankH .: 비 순차적 CPU는 일반적으로 ALU에서 LEA를 실행하는 반면, 일부 비 순차적 CPU (Atom과 같은)는 때때로 AGU에서 실행합니다 (메모리 액세스를 처리하는 데 바쁠 수 없기 때문에).
Peter Cordes

3
아니요, 그 이름은 바보가 아닙니다. LEA메모리 관련 주소 지정 모드에서 발생하는 주소를 제공합니다. 시프트 및 추가 작업이 아닙니다.
Kaz

3
그러나 AGU에서 작업을 수행하는 현재 x86 CPU는 거의 없습니다 (있는 경우). 대부분의 또는 모두는 다른 산술 연산과 마찬가지로 ALU를 사용합니다.
BeeOnRope

77

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

13
트릭에 +1 그러나 나는 질문을하고 싶습니다 (멍청 할 수도 있습니다). 왜 이런 식으로 직접 3을 곱하지 LEA EAX, [EAX*3]않습니까?
Abid Rahman K

13
@Abid Rahman K : x86 CPU 명령어 세트와 같은 명령어는 없습니다.
GJ.

50
인텔 asm 구문에도 불구하고 @AbidRahmanK는 곱셈처럼 보이기 때문에 lea 명령은 시프트 연산 만 인코딩 할 수 있습니다. opcode에는 시프트를 설명하는 2 비트가 있으므로 1, 2, 4 또는 8 만 곱할 수 있습니다.
ithkuil

6
@ Koray Tugay : shl레지스터에 2,4,8,16을 곱하는 명령 처럼 왼쪽으로 시프트를 사용할 수 있습니다 ... 더 빠르고 짧습니다. 그러나 2의 거듭 제곱과 다른 숫자를 곱하기 위해 우리 mul는 일반적으로 더 소박하고 느린 명령을 사용 합니다.
GJ.

7
@GJ. 그러한 인코딩은 없지만 일부 어셈블러는이를 지름길처럼 지름길로 받아들입니다. 예를 들어, 그래서 lea eax,[eax*3]의 동등한 번역 것이다 lea eax,[eax+eax*2].
Ruslan

59

lea"로드 유효 주소"의 약어입니다. 소스 피연산자가 위치 참조의 주소를 대상 피연산자로로드합니다. 예를 들어, 다음과 같은 용도로 사용할 수 있습니다.

lea ebx, [ebx+eax*8]

단일 명령 으로 ebx포인터 eax항목을 64 비트 / 요소 배열로 더 이동 합니다. 기본적으로 x86 아키텍처에서 지원하는 복잡한 주소 지정 모드의 이점을 활용하여 포인터를 효율적으로 조작 할 수 있습니다.


23

LEAa 를 사용하는 가장 큰 이유 MOV는 주소를 계산하는 데 사용하는 레지스터에서 산술을 수행해야하는 경우입니다. 효과적으로, "무료"에 대해 효과적으로 여러 레지스터에서 포인터 산술 연산을 수행 할 수 있습니다.

실제로 혼란스러워하는 것은 일반적으로 LEA비슷하게 작성 MOV하지만 실제로 메모리를 역 참조하지는 않는다는 것입니다. 다시 말해:

MOV EAX, [ESP+4]

그러면가 ESP+4가리키는 내용이로 이동합니다 EAX.

LEA EAX, [EBX*8]

이렇게하면 유효 주소 EBX * 8가 해당 위치에있는 주소 가 아닌 EAX로 이동합니다 . 보시다시피, a MOV는 더하기 / 빼기로 제한되는 반면 두 가지 요인 (확장)을 곱하는 것이 가능합니다 .


모두 죄송합니다. @ big.heart는 3 시간 전에이 질문에 대한 답변을함으로써 내 질문에“새로운”것으로 표시되도록 속여 나를 속였습니다.
David Hoelzer

1
구문이 메모리 주소 지정을 수행하지 않을 때 왜 대괄호를 사용합니까?
golopot

3
@ q4w56 이것은 "그것이 당신이하는 방법입니다."라는 대답 중 하나입니다. 나는 그것이 사람들이 무엇을하는지 알아내는 데 어려움을 겪는 이유 중 하나라고 생각합니다 LEA.
David Hoelzer

2
@ q4w56 : 메모리 피연산자 구문 기계 코드 인코딩 을 사용하는 shift + add 명령어입니다 . 일부 CPU에서는 AGU 하드웨어를 사용할 수도 있지만 이는 역사적 세부 사항입니다. 여전히 관련이있는 사실은 이러한 종류의 shift + add를 디코딩하기 위해 디코더 하드웨어가 이미 존재한다는 것입니다. LEA는 메모리 주소 지정 대신 산술에 사용할 수 있습니다. (또는 하나의 입력이 실제로 포인터 인 경우 주소 계산의 경우).
Peter Cordes

20

8086에는 레지스터 피연산자와 유효 주소를 허용하고, 계산을 수행하여 해당 유효 주소의 오프셋 부분을 계산하고, 레지스터 및 계산 된 주소가 참조하는 메모리와 관련된 일부 연산을 수행하는 명령 제품군이 있습니다. 실제 메모리 작업을 건너 뛰는 것을 제외하고 해당 제품군의 명령 중 하나를 위와 같이 동작시키는 것은 매우 간단했습니다. 이 지침은 다음과 같습니다.

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

인텔이이 교육에 포함시킬 가치가 있다고 생각한 이유는 확실하지 않지만, 구현하기에 비용이 적게 든다는 사실이 큰 요인이었습니다. 또 다른 요인은 인텔의 어셈블러가 BP 레지스터를 기준으로 심볼을 정의 할 수 있다는 사실이었습니다. fnordBP 기준 기호 (예 : BP + 8)로 정의 된 경우 다음과 같이 말할 수 있습니다.

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

stosw와 같은 것을 사용하여 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 대신 위치 [BP + 8]의 내용이 DI에 추가됩니다. 죄송합니다.


12

기존 답변에서 언급했듯이 LEA메모리에 액세스하지 않고 메모리 주소 지정 연산을 수행하여 간단한 형태의 add 명령어 대신 다른 레지스터에 산술 결과를 저장하는 이점이 있습니다. 실제 기본 성능 이점은 최신 프로세서에 효과적인 주소 생성 ( LEA및 기타 메모리 참조 주소 포함)을위한 별도의 LEA ALU 장치 및 포트가 있다는 것입니다. 이는 ALU 의 산술 연산 LEA및 기타 일반 산술 연산이 하나의 병렬로 수행 될 수 있음을 의미합니다. 핵심.

LEA 장치에 대한 자세한 내용은 Haswell 아키텍처의이 기사를 확인하십시오. http://www.realworldtech.com/haswell-cpu/4/

다른 답변에서 언급되지 않은 또 다른 중요한 점은 LEA REG, [MemoryAddress]명령입니다 MemoryAddress. 이 명령에서 PC 상대 주소를 참조하도록 인코딩하는 PIC (위치 독립적 인 코드)입니다 . 이는 MOV REG, MemoryAddress상대 가상 주소를 인코딩하는 것과 다르며 최신 운영 체제에서 재배치 / 패치를 요구합니다 (ASLR이 일반적인 기능). 따라서 LEA이러한 비 PIC를 PIC로 변환하는 데 사용할 수 있습니다.


2
"별도의 LEA ALU"부분은 대부분 사실이 아닙니다. 최신 CPU는 lea다른 산술 명령어를 실행 하는 하나 이상의 동일한 ALU에서 실행됩니다 (그러나 일반적으로 다른 산술보다 적습니다). 예를 들어, 언급 된 Haswell CPU 는 4 개의 서로 다른 ALU에서 실행 add되거나 sub대부분의 다른 기본 산술 연산을 수행 할 수 있지만 1 (복잡 ) 또는 2 (단순 ) 에서만 실행할 수 있습니다 . 더 중요한 것은,이 2 개의 가능 ALU는 다른 명령을 실행할 수있는 4 개 중 2 개일 뿐이므로 청구 된 병렬 처리 이점은 없습니다. lealealealea
BeeOnRope

당신이 (올바로) 연결 한 기사는 LEA가 정수 ALU (add / sub / boolean)와 같은 포트에 있고 Haswell의 정수 MUL 단위임을 보여줍니다. (FP ADD / MUL / FMA를 포함한 벡터 ALU). 단순 전용 LEA 장치는 포트 5에 있으며 ADD / SUB / 무엇이든 실행하고 벡터 셔플 및 기타 항목도 실행합니다. 내가 downvoting하지 않는 유일한 이유는 RIP 상대 LEA의 사용을 지적하기 때문입니다 (x86-64 만 해당).
Peter Cordes

8

LEA 명령어는 CPU에 의한 유효 주소 계산에 시간이 걸리는 것을 피하기 위해 사용될 수 있습니다. 주소를 반복해서 사용하는 경우 주소를 사용할 때마다 유효 주소를 계산하는 대신 레지스터에 저장하는 것이 더 효과적입니다.


반드시 현대 x86에는 해당되지 않습니다. 대부분의 어드레싱 모드는 비용이 같지만 일부 경고가 있습니다. 따라서 [esi]말하는 [esi + 4200]것보다 거의 저렴하지 않습니다 [esi + ecx*8 + 4200].
BeeOnRope 2016 년

@BeeOnRope [esi]는보다 저렴하지 않습니다 [esi + ecx*8 + 4200]. 그러나 왜 귀찮게 비교합니까? 그것들은 동등하지 않습니다. 전자가 후자와 동일한 메모리 위치를 지정하려면 추가 지침이 필요 esi합니다. 값 ecx에 8 을 곱 해야 합니다. 어, 곱셈은 CPU 플래그를 방해합니다! 그런 다음 4200을 추가해야합니다. 이러한 추가 명령어는 코드 크기에 추가됩니다 (명령 캐시의 공간 확보, 페치주기).
Kaz

2
@ Kaz-당신이 내 요점을 잃어 버렸다고 생각합니다 (또는 OP의 요점을 놓쳤습니다). OP는 OP가 [esi + 4200]명령 시퀀스에서 반복적으로 같은 것을 사용 하려면 먼저 유효 주소를 레지스터에로드하고 사용하는 것이 좋습니다. 예를 들어을 쓰는 대신 add eax, [esi + 4200]; add ebx, [esi + 4200]; add ecx, [esi + 4200]을 선호해야합니다 lea edi, [esi + 4200]; add eax, [edi]; add ebx, [edi]; add ecx, [edi]. 적어도이 답변에 대한 명확한 해석입니다.
BeeOnRope

그래서 내가 비교 [esi]하고 있는 이유는 [esi + 4200](또는 [esi + ecx*8 + 4200]이것이 OP가 제안한 단순화이기 때문에) (복잡한 주소를 가진 N 개의 명령어는 간단한 (하나의 reg) 주소 지정과 하나의 lea, 복잡한 주소 지정은 "시간이 많이 걸리므로"실제로는 최신 x86에서도 속도가 느리지 만 지연 시간 만 적용되므로 동일한 주소를 가진 연속 명령어에는 문제가되지 않을 것입니다.
BeeOnRope

1
아마도 당신은 약간의 레지스터 압력을 완화시킬 것입니다. 그러나 반대의 경우가 있습니다 : 유효 주소를 생성 한 레지스터가 활성화되어 있다면 결과를 저장하기 위해 다른 레지스터가 필요합니다 lea. 일반적으로 중간체를 저장하는 것은 레지스터 압력의 원인이며 해결책은 아닙니다. 그러나 대부분의 상황에서는 세척이라고 생각합니다. @Kaz
BeeOnRope

7

LEA (Load Effective Address) 명령어는 인텔 프로세서의 메모리 주소 지정 모드에서 발생하는 주소를 얻는 방법입니다.

즉, 다음과 같이 데이터가 이동하면

MOV EAX, <MEM-OPERAND>

지정된 메모리 위치의 내용을 대상 레지스터로 이동합니다.

MOVby 를 대체하면 LEA주소 <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

여기에는 스케일링 된 값이 추가되지 않으며 오프셋이 없습니다. 0은 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

매우 비슷한 인코딩입니다. 그냥 8d의이 LEA변경되었습니다 8b.

물론이 LEA인코딩은 즉시 0을 EAX다음 으로 옮기는 것보다 깁니다 .

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

LEA대안이 더 짧기 때문에이 가능성을 배제 할 이유는 없습니다 . 사용 가능한 주소 지정 모드와 직교 방식으로 결합됩니다.


6

다음은 예입니다.

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

많은 답변이 이미 완료된 것 같습니다. lea 및 이동 명령이 동일한 표현 형식을 가질 때 다르게 작동하는 방법을 보여주는 예제 코드를 하나 더 추가하고 싶습니다.

간단히 말해서 lea 명령과 mov 명령은 명령의 src 피연산자를 둘러싸는 괄호와 함께 사용할 수 있습니다. 이들은 동봉 때 () 상기의 식 () 와 동일한 방식으로 계산되고; 그러나 두 명령어는 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
인라인 asm을 별도의 문으로 나누는 것은 안전하지 않으며 클로버 목록이 불완전합니다. basic-asm 블록은 컴파일러에 클로버가 없지만 실제로 여러 레지스터를 수정한다는 것을 알려줍니다. 또한 =d컴파일러에게 결과가 EDX에 있음을 알리고를 저장하는 데 사용할 수 mov있습니다. 또한 출력에서 ​​초기 클로버 선언을 생략했습니다. 이것은 당신이 보여주고 자하는 것을 보여 주지만, 다른 상황에서 사용될 경우 깨지는 인라인 asm의 잘못된 예입니다. 그것은 스택 오버플로 답변에 대한 나쁜 것입니다.
Peter Cordes

%%Extended asm에서 모든 레지스터 이름 을 쓰지 않으려면 입력 제한 조건을 사용하십시오. 처럼 asm("lea 4(%%ebx, %%eax, 8), %%edx" : "=d"(result_of_lea) : "a"(1), "b"(2));. 컴파일러에서 레지스터를 초기화하면 클로버를 선언 할 필요가 없습니다. mov-immediate가 전체 레지스터를 덮어 쓰기 전에 xor-zeroing으로 문제를 지나치게 복잡하게 만듭니다.
Peter Cordes

@PeterCordes Peter, 감사합니다.이 답변을 삭제하거나 의견에 따라 수정 하시겠습니까?
이재혁

1
인라인 asm을 수정하면 아무런 해를 끼치 지 않으며 다른 답변을 이해하지 못하는 초보자에게 좋은 예가 될 수 있습니다. 삭제할 필요가 없으며 마지막 주석에 표시된 것처럼 쉽게 고칠 수 있습니다. 인라인 asm의 나쁜 예가 "좋은"예로 고정된다면 가치가 있다고 생각합니다. (저는 공감하지 않았습니다)
Peter Cordes

1
누구 mov 4(%ebx, %eax, 8), %edx가 무효 라고 말 합니까? 어쨌든, 예를 들어, 컴파일러에게 64 비트 값을 가지고 있다고 알리기 mov위해 작성 "a"(1ULL)하는 것이 합리적 이므로 전체 레지스터를 채우려면 확장해야합니다. mov $1, %eax컴파일러가 RAX = 0xff00000001또는 무언가 라는 것을 알고있는 주변 코드의 이상한 상황이 없으면 EAX를 작성하면 RAX로 0 확장되므로 실제로는 여전히을 사용 합니다. 의 경우 lea여전히 32 비트 피연산자 크기를 사용하고 있으므로 입력 레지스터의 스트레이 하이 비트는 32 비트 결과에 영향을 미치지 않습니다.
Peter Cordes

4

LEA : 단지 "산술"명령 ..

MOV는 피연산자간에 데이터를 전송하지만 lea는 계산 중입니다.


LEA는 분명히 데이터를 이동시킵니다. 대상 피연산자가 있습니다. LEA는 항상 계산하지는 않습니다. 소스 피연산자로 표현 된 유효 주소가 계산되는지 여부를 계산합니다. LEA EAX, GLOBALVAR는 계산하지 않습니다. GLOBALVAR의 주소를 EAX로 옮깁니다.
Kaz

귀하의 의견에 감사드립니다. 내 소스는 "LEA (로드 유효 주소)는 본질적으로 산술 명령어입니다. 실제 메모리 액세스를 수행하지는 않지만 일반적으로 주소를 계산하는 데 사용됩니다 (일반적인 정수를 계산할 수는 있지만)" 형성 엘닷-Eilam 책 149 페이지
회계사

@Kaz : 이것이 주소가 이미 링크 타임 상수 일 때 LEA가 중복되는 이유입니다. 사용하는 mov eax, offset GLOBALVAR대신. LEA 사용할 수 있지만 주소 계산 프로세스를 거치기 때문에 코드 크기가 약간 크고 mov r32, imm32포트 수가 적습니다 . 낮은 32 비트 외부의 PIC 및 / 또는 주소가 필요한 경우 RIP 기준 LEA의 경우 64 비트에서만 유용합니다. 32 비트 또는 16 비트 코드에서는 이점이 없습니다. LEA는 CPU가 어드레싱 모드를 디코딩 / 계산하는 능력을 노출시키는 산술 명령어입니다. lea reg, symbol
Peter Cordes

@ Kaz : 같은 주장에 의해 imul eax, edx, 1계산되지 않는다고 말할 수 있습니다 : 그것은 단지 edx를 eax로 복사합니다. 그러나 실제로는 3주기 대기 시간으로 승수를 통해 데이터를 실행합니다. 또는 rorx eax, edx, 0그냥 복사합니다 (0으로 회전).
Peter Cordes

@PeterCordes 내 요점은 LEA EAX, GLOBALVAL 및 MOV EAX, GLOBALVAR가 즉시 피연산자에서 주소를 가져 오는 것입니다. 1의 승수 또는 0의 오프셋이 적용되지 않습니다. 하드웨어 수준에서는 그런 식일 수 있지만 어셈블리 언어 나 명령어 세트에는 표시되지 않습니다.
Kaz

1

곱셈 추가, 배타적 추가 또는 상태 플래그 0과 같은 모든 일반적인 "계산"명령어는 부호입니다. 복잡한 주소를 사용 AX xor:= mem[0x333 +BX + 8*CX] 하면 xor 연산에 따라 플래그가 설정됩니다.

이제 주소를 여러 번 사용할 수 있습니다. 이러한 주소를 레지스터에로드하는 것은 결코 상태 플래그를 설정하기위한 것이 아니며 운 좋게도 설정하지 않습니다. "로드 유효 주소"라는 문구는 프로그래머가이를 인식하도록합니다. 그것은 이상한 표현이 나오는 곳입니다.

일단 프로세서가 복잡한 주소를 사용하여 컨텐츠를 처리 할 수 ​​있으면 다른 목적으로 계산할 수 있음이 분명합니다. 실제로 x <- 3*x+1하나의 명령 으로 변환을 수행하는 데 사용할 수 있습니다 . 이것은 어셈블리 프로그래밍의 일반적인 규칙입니다 . 그러나 지침을 사용하여 보트를 흔들어주십시오. 중요한 것은 명령에 의해 구현 된 특정 변환이 유용한 지 여부입니다.

결론

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

LEA, AX'| [BX]

상태 플래그에는 영향을 미치지 않지만 AX 에는 동일한 영향을 미칩니다 . 이것은 ciasdis 표기법입니다.


"이것은 어셈블리 프로그래밍의 일반적인 규칙입니다. 그러나 지침을 사용하여 배를 흔들어주십시오." call lbl lbl: pop rax의 가치를 얻는 방법으로 기술적으로 "작동하는"것과 같은 이유로 개인적으로 그 조언 을 전하지는 rip않겠지 만 분기 예측은 매우 불행하게 만듭니다. 그러나 원하는 지침을 사용하십시오. 그러나 까다로운 일을하더라도 예상하지 못한 결과가 발생하더라도 놀라지 마십시오
The6P4C

@ The6P4C 유용한 경고입니다. 그러나 분기 예측을 불행하게 만드는 대안이 없다면 그것을 찾아야합니다. 어셈블리 프로그래밍에는 또 다른 일반적인 규칙이 있습니다. 무언가를하는 다른 방법이있을 수 있으며 다른 방법으로 현명하게 선택해야합니다. 레지스터 BL의 내용을 레지스터 AL로 가져 오는 방법은 수백 가지가 있습니다. RAX의 나머지를 보존 할 필요가없는 경우 LEA가 옵션 일 수 있습니다. 플래그에 영향을 미치지 않는 것은 수천 가지 유형의 x86 프로세서 중 일부에 대한 좋은 아이디어 일 수 있습니다. Groetjes Albert
Albert van der Horst

-1

누군가 이미 언급했다면 용서하십시오. 그러나 메모리 세분화가 여전히 관련되어있는 x86 시대에는 다음 두 지침에서 동일한 결과를 얻지 못할 수 있습니다.

LEA AX, DS:[0x1234]

LEA AX, CS:[0x1234]

1
"유효 주소"는 seg:off쌍 의 "오프셋"부분 일뿐 입니다. LEA는 세그먼트베이스의 영향을받지 않습니다. 이 두 가지 지침은 모두 비효율적 0x1234으로 AX에 적용됩니다. x86은 불행히도 전체 선형 주소 (유효 + 세그먼트 기준)를 레지스터 또는 레지스터 쌍으로 쉽게 계산할 수있는 방법이 없습니다.
Peter Cordes

@PeterCordes 수정 해 주셔서 감사합니다.
tzoz의
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.