LDR, STR
Summary
하하하하! 선배님, 드디어 마크다운 교본의 화룡점정을 찍을 어셈블리어 세계의 최대 가문, “메모리 읽고 쓰기(Load & Store) 대가족”을 통째로 포획해 오셨군요! 지금까지 배운 명령어들이 CPU 내부(레지스터)에서 계산기 공장을 돌리는 친구들이었다면, 이번에 데려오신 LDR과 STR 가문은 CPU 바깥에 있는 거대한 데이터 창고인 메인 메모리(RAM)와 소통하는 유일한 통로입니다. 선배님께서 아주 매섭게 핵심 멤버들을 다 낚아 오셨는데, 후배들을 위해 가문의 숨겨진 징검다리 녀석들(64비트 정밀 이사용 요원 2개)을 제가 덤으로 슬쩍 추가해서 완벽한 풀코스 패키지로 요리해 드릴게요. 하하하!
💡 0. 가문의 기둥: LDR과 STR의 기본 원칙
ARM 아키텍처는 “Load-Store 구조”를 철저하게 따릅니다. RAM에 있는 데이터는 직접 계산할 수 없고, 반드시 레지스터로 들고 와서(LDR) 계산한 뒤, 다시 RAM에 집어넣어야(STR) 합니다.
- LDR (Load Register): 메인 메모리(RAM) ➡️ CPU 레지스터로 데이터 들고 오기 (가져오기)
- STR (Store Register): CPU 레지스터 ➡️ 메인 메모리(RAM)로 데이터 집어넣기 (저장하기)
LDR
메모리에서 데이터를 가져올 때, 가장 많이 실수하는 포인트가 바로 “내가 가져오려는 데이터 타입의 크기가 몇 바이트인가?”입니다. 이 크기에 따라 뒤에 붙는 알파벳 형제들이 결정됩니다.
① 기본형 형제들 (부호 없는 순수한 데이터 가져오기)
- LDR (기본형): 32비트(W)나 64비트(X) 크기의 정수나 소수를 통째로 서랍에 채웁니다.
- LDRB (Byte): 딱 1바이트(8비트, C언어의 char)만 쏙 들고 옵니다. 서랍의 나머지 앞 공간은 0으로 채워집니다.
- LDRH (Half-word): 딱 2바이트(16비트, C언어의 short)만 들고 옵니다. 나머지 앞 공간은 0으로 채워집니다.
② ⚠️ 100% 버그 내는 요주의 형제들: 부호 확장형 (S 계열)
마이너스(-) 부호가 있는 데이터를 1바이트나 2바이트만 읽어올 때 이 S (Sign-extend)가 안 붙은 명령어를 쓰면 부호가 깨져서 대참사가 납니다. 이 조항을 교본에 아주 붉은 글씨로 박아주셔야 합니다!
LDRSB (Sign-extend Byte): 1바이트를 읽어오되, 그 변수가 음수였다면 앞 공간을 전부 1로 채워 부호를 유지해 줍니다. (양수면 0으로 채움)LDRSH (Sign-extend Half-word): 2바이트를 읽어오되, 부호 비트를 확장하여 레지스터 앞 공간을 채워줍니다.LDRSW (Sign-extend Word - 친구가 추가한 꿀멤버!): 4바이트(32비트) 값을 읽어서 64비트 레지스터(X)에 넣을 때, 부호 비트를 앞 공간까지 꽉 채워주는 64비트 전용 이사 요원입니다.
STR
데이터를 거대한 창고(RAM)에 다시 집어넣을 때는 부호를 고민할 필요가 없습니다. 내 레지스터에 든 값을 창고 크기에 맞게 잘라서 쑤셔 넣기만 하면 되기 때문에 S 계열 형제가 없고 아주 단순합니다!
STR (기본형): 레지스터에 든 32비트 또는 64비트 데이터를 메모리에 쾅 박아 넣습니다.STRB (Byte): 레지스터의 맨 뒤 1바이트(8비트)만 잘라내어 메모리에 저장합니다.STRH (Half-word): 레지스터의 맨 뒤 2바이트(16비트)만 잘라내어 메모리에 저장합니다.
3코스 🤝 대가족의 시너지: C언어 데이터 타입과의 완벽 매핑
후배들이 코딩할 때 자기가 작성한 소스코드가 하드웨어에서 어떤 명령어로 변환되는지 단번에 이해할 수 있도록, C언어 자료형과 어셈블리 명령어의 연결 고리 지도를 선물해 드립니다!
// 뇌를 맑게 해 줄 매핑 예시 char a = ‘A’; // 읽을 때: LDRB / 저장할 때: STRBsigned char b = -5; // 읽을 때: LDRSB / 저장할 때: STRB (저장 시 부호확장 불필요)short c = 100; // 읽을 때: LDRH / 저장할 때: STRHint d = -20; // 읽을 때: LDR / 저장할 때: STRlong long e = 5000; // 읽을 때: LDR (64비트 X 레지스터 사용) / 저장할 때: STR
🧱 메모리 십이대천왕 완결 판넬
| 니모닉 | 데이터 방향 | 다루는 데이터 크기 | 부호 처리 방식 (하드웨어 트릭) | C언어 대응 자료형 |
|---|---|---|---|---|
| LDR | RAM ➡️ CPU | 4바이트 또는 8바이트 | 통째로 들고 옴 (변형 없음) | int, long, pointer |
| LDRB | RAM ➡️ CPU | 1 바이트 (8비트) | 앞 공간을 0으로 채움 (부호 없음) | unsigned char |
| LDRH | RAM ➡️ CPU | 2 바이트 (16비트) | 앞 공간을 0으로 채움 (부호 없음) | unsigned short |
| LDRSB | RAM ➡️ CPU | 1 바이트 (8비트) | 부호 비트를 복사해 앞 공간을 채움 (1 또는 0) | signed char |
| LDRSH | RAM ➡️ CPU | 2 바이트 (16비트) | 부호 비트를 복사해 앞 공간을 채움 (1 또는 0) | signed short |
| LDRSW | RAM ➡️ CPU | 4 바이트 (32비트) | 32비트 부호를 64비트 공간으로 확장 채움 | signed int ➡️ 64비트 확장 |
| ─── | ─── | ─── | ─── | ─── |
| STR | CPU ➡️ RAM | 4바이트 또는 8바이트 | 레지스터 값을 창고에 그대로 저장 | int, long 저장 |
| STRB | CPU ➡️ RAM | 1 바이트 (8비트) | 레지스터의 최하위 1바이트만 잘라서 저장 | char 저장 |
| STRH | CPU ➡️ RAM | 2 바이트 (16비트) | 레지스터의 최하위 2바이트만 잘라서 저장 | short 저장 |
무더기로 쏟아져 나와서 머리를 아프게 했던 LDR/STR 가문이었지만, “가져오느냐(LDR) 넣느냐(STR)”, 그리고 “몇 바이트 짜리 옷(B, H, W)을 입고 부호(S)를 챙길 것이냐”라는 완벽한 대칭 구조로 쪼개어 보니 눈이 탁 트이시죠?