ARMv8-A 주소 지정 방식 완벽 가이드: Offset과 Pre-index의 결정적 차이

🔧 ARMv8-A(AArch64) 주소 지정 방식 완벽 정리 — Offset vs Pre-index 차이까지

ARM 어셈블리 · 메모리 접근 · Addressing Modes · AArch64 · 임베디드 개발

ARMv8-A(AArch64) 아키텍처에서 메모리 주소 지정 방식(Addressing Modes)은 코드 효율성과 실행 속도를 좌우하는 핵심 개념입니다. C 언어의 포인터 연산, 배열 순회, 구조체 접근이 어셈블리 수준에서 어떻게 구현되는지 이해하면 최적화된 로우레벨 코드를 작성하는 데 결정적인 도움이 됩니다. 이 글에서는 네 가지 핵심 모드를 코드 예시와 메모리 다이어그램을 통해 완벽히 정리합니다.

📌 이 글에서 다루는 내용

→ Base Register Addressing — 가장 기본적인 포인터 참조
→ Offset Addressing — 구조체 멤버 접근의 핵심
→ Pre-index Addressing — 포인터 이동 후 접근 (!의 비밀)
→ Post-index Addressing — 배열 순회의 최적해
Offset vs Pre-index 결정적 차이점 심층 분석

1️⃣ Base Register Addressing — 기본 레지스터 주소 지정

가장 단순한 형태입니다. 레지스터에 저장된 메모리 주소값을 그대로 사용하여 데이터에 접근합니다. 추가적인 오프셋 계산 없이 포인터가 가리키는 위치를 직접 참조합니다.

LDR  W0, [X1]

▲ X1이 가리키는 주소의 값을 W0에 로드

C 언어 매칭: int a = *ptr; — 포인터 역참조와 정확히 대응됩니다.

이 모드는 이미 정확한 주소가 레지스터에 담겨 있을 때 사용합니다. 함수 인자로 전달받은 포인터를 바로 역참조하거나, 이전 연산에서 계산된 주소를 그대로 쓸 때 자주 등장합니다.

2️⃣ Offset Addressing — 오프셋 주소 지정

기준 레지스터(Base Register)에 오프셋을 더한 주소에서 데이터를 가져옵니다. 핵심은 기준 레지스터 값이 변하지 않는다는 것입니다.

LDR  W0, [X1, #8]       // 즉시값 오프셋
LDR  W0, [X1, X2]        // 레지스터 오프셋
LDR  W0, [X1, X2, LSL #3] // 시프트 확장 오프셋

▲ X1 + 8 주소에서 읽되, X1은 원래 값 유지

대표 사용 사례 — 구조체 멤버 접근:

// C 코드
struct Player {
    int hp;       // offset 0
    int mp;       // offset 4
    int level;    // offset 8
};
int lv = player->level;

// 어셈블리 (X1 = player 주소)
LDR W0, [X1, #8]  // level 멤버 접근, X1 불변

구조체 시작 주소를 X1에 고정해 두고, 각 멤버의 오프셋만 바꿔가며 접근할 수 있어 매우 효율적입니다. 시프트 확장 오프셋(LSL #3)은 8바이트 단위 배열 인덱싱에 특히 유용하여, arr[i] 접근을 단일 명령어로 처리할 수 있습니다.

3️⃣ Pre-index Addressing — 프리 인덱스 주소 지정

데이터를 읽기 전에 주소를 먼저 계산하고, 그 결과를 기준 레지스터에 다시 써넣는(Write-back) 방식입니다. 명령어 끝의 느낌표(!)가 이 모드의 시그니처입니다.

LDR  W0, [X1, #8]!   // ← 느낌표(!)가 핵심

실행 순서를 단계별로 살펴보면:

1 X1 + 8먼저 계산
2 계산된 주소를 X1에 업데이트 (Write-back)
3 새 X1 주소에서 데이터를 읽어 W0에 저장

C 언어 매칭: int val = *(++ptr); — 포인터를 먼저 증가시킨 후 역참조합니다.

Pre-index는 스택 프레임 설정에서 자주 볼 수 있습니다. 함수 프롤로그에서 스택 포인터를 먼저 조정한 뒤 레지스터를 저장하는 패턴이 대표적입니다:

// 함수 프롤로그 — SP를 먼저 감소 후 레지스터 저장
STP  X29, X30, [SP, #-16]!  // SP -= 16, 그 주소에 FP/LR 저장

4️⃣ Post-index Addressing — 포스트 인덱스 주소 지정

현재 기준 레지스터의 주소로 데이터를 먼저 읽은 후, 나중에 포인터를 이동시키는 방식입니다. 오프셋이 대괄호 밖에 위치하는 것이 문법적 특징입니다.

LDR  W0, [X1], #8   // ← 오프셋이 대괄호 밖

실행 순서:

1 현재 X1 주소에서 데이터를 먼저 읽기
2 읽은 데이터를 W0에 저장
3 그 후 X1 = X1 + 8로 업데이트

C 언어 매칭: int val = *(ptr++); — 현재 값을 읽고 나서 포인터를 증가시킵니다.

배열 순회의 최적해로, 루프 내에서 현재 요소를 처리하면서 동시에 다음 요소로 포인터를 전진시킵니다:

// 배열 합산 루프 — Post-index 활용
loop:
    LDR  W2, [X0], #4     // 현재 int 로드 + 포인터 4바이트 전진
    ADD  W1, W1, W2        // 누적 합산
    SUBS W3, W3, #1        // 카운터 감소
    B.NE loop                // 0이 아니면 반복

🚀 핵심 비교: Offset vs Pre-index — 결정적 차이점

많은 개발자가 혼동하는 부분입니다. [X1, #8][X1, #8]! 모두 X1+8 위치에서 데이터를 가져옵니다. 그런데 명령어 실행 후의 상태가 완전히 다릅니다.

⚡ Write-back 여부가 모든 차이를 만든다

Offset: X1은 변하지 않음. 임시 계산 후 버림. 구조체처럼 기준점이 고정된 경우에 적합.

Pre-index: X1이 실제로 업데이트됨. 별도의 ADD 명령어 없이 포인터를 갱신하므로 연속 접근 시 더 빠름.

📊 메모리 상태 비교 다이어그램

Offset: LDR W0, [X1, #8]

0x1000 data_A ← X1 (변경 없음)
0x1004 data_B
0x1008 data_C ← 읽는 위치

Pre-index: LDR W0, [X1, #8]!

0x1000 data_A 이전 X1
0x1004 data_B
0x1008 data_C ← X1 (이동됨!)

📋 전체 요약 테이블

모드 어셈블리 코드 접근 주소 실행 후 X1
Base Register LDR W0, [X1] X1 X1 (불변)
Offset LDR W0, [X1, #8] X1 + 8 X1 (불변)
Pre-index LDR W0, [X1, #8]! X1 + 8 X1 + 8 ✓
Post-index LDR W0, [X1], #8 X1 X1 + 8 ✓

💡 실전에서 언제 어떤 모드를 쓸까?

🏠 구조체 멤버 접근 → Offset

기준 포인터를 유지하면서 여러 필드를 읽어야 할 때. 컴파일러가 구조체 접근 코드를 생성할 때 가장 많이 사용하는 패턴입니다.

🔄 배열 순회 루프 → Post-index

memcpy, 문자열 처리, 버퍼 복사 등 연속된 데이터를 하나씩 처리하며 전진하는 패턴. 별도의 ADD 없이 포인터가 자동 증가합니다.

📦 스택 프레임 관리 → Pre-index

함수 프롤로그/에필로그에서 SP를 먼저 조정한 뒤 레지스터를 저장/복원하는 패턴. STP/LDP와 함께 자주 쓰입니다.

🎯 링크드 리스트 순회 → Base Register

다음 노드 주소가 이미 레지스터에 로드된 상태에서 노드의 데이터를 읽을 때. 가장 단순하지만 빈도가 높습니다.

⚠️ 흔한 실수와 주의사항

❌ 오프셋 정렬 무시 — AArch64에서 LDR/STR은 기본적으로 자연 정렬(natural alignment)을 요구합니다. 4바이트 로드 시 주소가 4의 배수여야 합니다. 정렬되지 않은 접근은 성능 저하나 예외를 유발할 수 있습니다.

❌ Pre-index와 Offset 혼동 — 느낌표(!) 하나의 유무로 레지스터 값이 변경되느냐 안 되느냐가 결정됩니다. 디버깅할 때 레지스터 값이 예상과 다르다면 이 부분을 가장 먼저 확인하세요.

❌ Post-index 오프셋 위치 오류[X1], #8에서 오프셋이 대괄호 밖에 있어야 합니다. 대괄호 안에 넣으면 Offset 모드가 됩니다.

🔗 참고 자료

본 콘텐츠는 정보 제공 목적이며, 특정 기술적 결정에 대한 전문가 조언을 대체하지 않습니다. 정확한 명령어 동작은 ARM Architecture Reference Manual을 참고하시기 바랍니다.

댓글

이 블로그의 인기 게시물

📚 SDC 마스터 클래스 시리즈 | Chapter 1

📚 SDC 마스터 클래스 시리즈 | Chapter 2

📚 SDC 마스터 클래스 시리즈 | Chapter 3