지난 글에서는 메모리와 레지스터 사이 데이터를 주고받는 방법을 정리해보았다.
간단히 정리하면,
ld [메모리주소], 레지스터 형태로 메모리에서 값을 불러온다.
메모리주소는 불러올 데이터 크기의 배수여야한다.
만약 ldd 로 8byte 를 불러온다면 레지스터는 짝수번째 레지스터여야한다.
메모리주소는 레지스터와 레지스터(상수) 합으로 표현되며, -레지스터만 없으면 된다.
st 레지스터, [메모리주소] 형태로 레지스터 값을 메모리에 저장한다.
마찬가지로 메모리 주소는 저장할 데이터 크기의 배수여야 하고
만약 std 로 8byte 를 저장한다면 레지스터는 짝수번째 레지스터여야 한다.
메모리주소는 레지스터와 레지스터(상수) 합으로 표현되며, -레지스터만 없으면 된다.
이번 글에서는 메모리의 영역 중 static 영역에 데이터를 할당하는 방법을 정리해본다.
그리고 어셈블하기 전에 미리 메모리에 데이터를 할당해둘 수 있는데, 이때 메모리에
데이터를 어떤 식으로 저장하는지, 그리고 그 때 발생할 수 있는 문제와 해결방법인 경계정렬에 대해서도 정리해본다.
정적 메모리
SPRAC의 메모리 맵을 보면 아래와 같이 되어 있다.
이번에 정리할 부분은 Static 영역 중에서 text 영역과 data 영역이다.
스택 영역은 전에 정리했던 save 명령어로 할당하게 되고, 아마도 heap 영역은 지난 글에서 정리했던 ld 명령어로 할당하는 듯하다.
text 영역은 세그먼트라고도 하는데, 코드가 들어가는 위치이다.
text 영역에 들어가는 데이터는 모두 read-only 데이터인데, 소스코드는 수정할 일이 없으니 읽기 전용인 것이 당연하다.
그 밖에도 수정할 일 없는 전역 정적 변수를 저장하기도 한다.
data 영역은 초기화가 필요한 데이터를 저장하는 공간이다.
각 영역은 아래와 같은 방법으로 그 영역의 시작임을 선언한다.
.section ".data"
.section ".text"
또는
.data
.text
영역을 선언하고 그 아래에 작성한 내용이 그 영역에 저장되는 내용이다.
hello 를 출력하는 간단한 프로그램이다.
뒤에 정리하겠지만, data 영역에 문자열을 미리 저장해두고,
저장한 문자열을 레지스터로 가져와서 printf 를 통해 출력하는 간단한 프로그램을 작성하였다.
주석에 표시된 것처럼 .section 을 사용하여 영역을 표시해도 좋고, .data 로 바로 영역을 표시해도 좋다.
data 영역에서는 메모리에 미리 값을 할당해둘 수 있다.
이때 사용하는 pseudo instruction 으로 아래와 같은 명령어가 있다.
.word | 4byte 공간에 데이터 할당 |
.half | 2byte 공간에 데이터 할당 |
.byte | 1byte 공간에 데이터 할당 |
.ascii | 여러 문자를 묶어 문자열로 할당 |
.asciz | 문자열 뒤에 \0 까지 추가 (아스키 제로) |
.align | 다음에 추가할 메모리 주소 정렬 |
.skip | 공간만 확보하고 초기화는 하지 않는다. |
.align 명령어는 뒤에서 자세히 설명하고, skip 명령어는 지금 글에서는 설명하지 않는다.
각 명령어는 아래와 같이 사용한다.
.data
.word 9
.half 10
.byte 12, 14, 15*4, 15 * 4 >> 2 ! , 를 이용해 여러데이터를 할당할 수도 있다.
.ascii "hello" ! == .byte "h", "e", "l", "l", "o"
.asciz "hello" ! == .ascii "hello" .byte "0"
데이터가 메모리에 할당될 때는, data 영역의 낮은 곳부터 순차적으로 데이터가 할당된다.
, 로 구분한 경우엔 왼쪽부터 차례대로 할당된다.
2byte, 1byte, 1byte 로 할당한 다음, 0x00ff 데이터가 저장된 위치에서 word 만큼 레지스터로 load 하면
0x00ff1234 라는 데이터로 묶어서 한번에 가져오게된다.
그런데 이렇게 서로 사이즈가 다른 상황에서 연속적으로 데이터를 저장하다보면 문제가 발생할 수도 있다.
이전 글에서도 말했지만, '메모리에 데이터를 저장할 때, 메모리의 주소는 데이터의 사이즈의 배수여야한다.'
하지만 아래와 같은 케이스를 보자.
word 3, byte 5 까지는 문제없이 저장되었지만 이후에 half 데이터를 저장할 때, 저장하는 메모리 주소가 홀수이다.
그 다음 word 가 저장되는 메모리주소도 홀수이다.
이런 경우에는 문제가 생긴다.
한번 코드를 돌려보자.
data section에서 메모리에 값을 쓰고, 5와 6 데이터를 word 사이즈 공간에 할당한 뒤
이를 프로그램에서 불러오도록 했다.
어셈블도 문제없이 되고, 프로그램 실행도 문제없이 되지만 프로그램을 실행시켜 보면
이렇게 에러가 발생한다.
에러가 발생한 이유는 word 데이터를 저장하는데 주소가 4의 배수가 아닌 2의 배수가 되었기 때문이다.
주소값이 4의 배수가 되도록, 중간에 half 사이즈 할당을 한번 해주면
이렇게 의도대로 값을 읽어와서 출력이 된다.
이렇게 메모리 주소값이 메모리에 담긴 데이터 사이즈의 배수로 설정되지 않은 채로 데이터가 들어간 상황을
'경계정렬이 되지 않았다' 라고 표현한다.
이를 해결해주는, 즉 경계정렬 시켜주는 방법은 2가지가 있다.
1) .align pseudo instruction 사용
2) greedy 하게 큰 사이즈부터 할당
먼저 .align 명령어를 사용하는 과정을 보자.
align 명령어는 아래와 같이 사용한다.
.align n ! 다음 공간의 메모리 주소를 n의 배수에서 시작하도록 맞춘다.
따라서 중간에 half 를 넣어 경계정렬을 맞췄던 위의 코드를 아래와 같이 수정할 수도 있다.
align 2 를 해도 문제는 없겠으나, align 의 의미가 퇴색되므로 4를 사용하는게 옳다.
실행에 문제가 없다.
두번째 경계정렬 방법은 사이즈가 큰 순으로 data 영역에 할당하는 것이다.
이는 4의배수는 동시에 2의 배수이면서 1의 배수이고, 2의 배수는 동시에 1의 배수이기 때문에
큰 사이즈부터 data 영역을 채워나가면 그 자체로 경계정렬이 된다.
위 예시 코드를 greedy 하게 경계정렬 시켜 문제를 해결하면 아래와 같다.
str 은 byte 의 연속이므로, byte 와 같은 수준에서 정렬된다.
실행시켜보면
역시 문제 없이 실행된다.
지금까지 정적메모리와, 정적메모리에 데이터를 쓰는 방법, 그 때 발생할 수 있는 '경계정렬' 문제와 해결방법을 정리하였다.
이 글까지가 학교에서 중간고사 범위이므로, 다음 글에서는 지금까지 배웠던 내용 중 중요한 내용을 요약하여 정리하고자 한다.
'CS > 어셈블리' 카테고리의 다른 글
[SPARC] 24. SPARC 메모리 맵 정리 ( + bss 영역) (0) | 2023.11.16 |
---|---|
[SPARC] 23. label로 data 영역의 메모리주소 가져오기 (set, sethi) (0) | 2023.11.09 |
[SPARC] 21. 메모리 (0) | 2023.10.19 |
[SPARC] 20. switch - case 구현하기 (0) | 2023.10.18 |
[SPARC] 19. Delay Slot Optimization (0) | 2023.10.17 |