일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- C언어
- struct반환
- GCC
- NASM
- void main
- C#
- sib
- stackalign
- effective address
- modrm
- csproj
- instruction
- OS
- modr/m
- movaps
- load effective address
- C
- call instruction
- 숏코딩
- disassemble
- movups
- return
- 어셈블리
- 운영체제
- WPF
- movdqu
- assembly
- struct
- 유효 주소
- compare
- Today
- Total
프로그래밍 잡화점
Load Effective Address 본문
어셈블리 기본 Instruction중 하나인 LEA(Load Effective Address)에 대해 알아보자.
Effective Address가 무엇인가?
예전에 어셈블리와 기계어에 대한 포스트에서 언급한 바 있는 SIB란 녀석이 있었다.
Base + Scale * Index로 계산되는데, 여기에 Displacement까지 더해서 나온 주솟값.
이 주솟값을 Effective Address, 한국어론 유효 주소라고 부른다.
그렇다면 어셈블리에서 LEA는 주로 어디에 사용되는가?
가장 간단한 예시로는 특정 레지스터의 주소를 기반으로 다른 주소를 구할 때 사용할 수 있을 것이다.
예를 들어, 가장 간단한 함수에서 사용하는 변수에 접근한다고 생각해보자.
그럼 다음과 같은 코드를 작성할 수 있을 것이다.
...
; int i : [ebp-4]
; eax = i;
mov eax, [ebp-4]
; i = 3;
mov eax, 3
mov [ebp-4], eax
...
그렇다면 변수의 주소를 얻어와야 한다면?
특정 레지스터에 ebp 주소를 넣고 sub 4를 할 수도 있을 것이다.
그러나 어셈블리에선 LEA 명령어를 통해서 더 간소화된 방식을 지원하는 것이다.
...
; int i : [ebp-4]
; eax = &i; no lea
mov eax, ebp
sub eax, 4
; eax = &i; use lea
lea eax, [ebp-4]
...
이 외에도 SIB에서 Scale과 Index를 사용하여 배열 인덱싱에도 적용할 수 있을 것이다.
(직접 코드를 작성해보기를 바란다.)
필자가 이 글을 쓰는데에는 추가적인 이유가 있다.
이러한 유효한 주소를 가져오는데에만 사용될 것만 같은 이 Instruction은 의외의 기능으로도 사용되고 있다.
다음 C언어 코드를 보자.
r = a + 4;
어셈블리에서는 다음과 같이 표현될 것이다.
...
; int r : [ebp-4], int a : [ebp-8]
; r = a + 4
mov eax, [ebp-8]
add eax, 4
mov [ebp-4], eax
...
이 코드에서 add 대신 재미난 방법을 사용해 이를 처리할 수 있다.
LEA는 해당 주소가 실제 접근할 수 없는 주소라도 연산을 해준다(!!)
즉, 위에 코드가 다음과 같이 될 수도 있다는 이야기이다.
...
; int r : [ebp-4], int a : [ebp-8]
; r = a + 4
mov eax, [ebp-8]
lea eax, [eax+4]
mov [ebp-4], eax
...
"엥? 별 차이 없는거 아니에요? 그냥 add하면 되지 왜 lea를 써요?"
맞는 이야기이다만, 이번에는 다른 예제를 살펴보자.
r = a * 4 + 3;
이번에는 식이 조금 더 복잡해졌다. 어셈블리 코드를 보자.
...
; int r : [ebp-4], int a : [ebp-8]
; r = a * 4 + 3
mov eax, [ebp-8]
; mul 최적화. 나중에 포스팅을 통해 따로 다룰 예정.
shl eax, 2
add eax, 3
mov [ebp-4], eax
...
그럼 이 코드를 LEA를 사용해서 만들어 볼 수 있을까?
...
; int r : [ebp-4], int a : [ebp-8]
; r = a * 4 + 3
mov eax, [ebp-8]
lea eax, [eax*4+3]
mov [ebp-4], eax
...
훨씬 간단한 코드가 만들어졌다.
(SIB의 특성상, Scale에는 1, 2, 4, 8 (3, 5, 9) 만 사용될 수 있음에 주의.)
이 뿐만 아니라, SIB의 특성상 상수를 제외하고도 레지스터가 추가적으로 들어갈 수 있어, 더 많은 단축이 가능한 셈이다.
LEA를 사용하는데 있어 좋은 점은 이뿐만이 아니다.
lea를 사용하게 되면, 연산을 하면서 생기는 레지스터 소모가 사라진다는 것이다.
즉, 특정 레지스터의 값을 바꾸지 않으면서 연산을 진행하여 결과를 도출해낼 수 있다는 의미이다.
이러한 LEA의 사용은 많은 상황에서 쓰일 수 있는 것은 아니지만, 간간히 특정 식에 대해 최적화를 진행할 수 있을 것이다.