지난 글에서는 다차원 배열에 대한 내용을 정리하였다.
row-major, column-major 에 따라 주소값을 구하는 방식이 달라지는 것에 유의하여야 함을 기억하자.
또, 배열 원소 하나의 사이즈에 따라 주소값 계산식에 W 를 다르게 넣어야 함도 유의하여야 했다.
이번 글에서는 스택 메모리 공간에 구조체를 저장할 때 어떻게 저장되는지 정리하고자 한다.
구조체가 메모리에 들어갈 때
다음과 같은 구조체가 있다고 해보자.
struct s {
int a, b;
char c;
short d, e;
int f, g;
}
이때 이 구조체 변수 하나를 스택에 선언하면, 실제 메모리에는 어떻게 저장될까?
구조체도 배열과 마찬가지로 선언된 순서대로 점점 주소가 높아진다. (즉, 마지막에 선언된 변수가 %fp에 가깝다)
즉, 위 그림과 같은 순서로 배치가 된다.
그런데 구조체와 배열의 차이가 있다면, 구조체는 내부에 들어있는 변수의 사이즈가 배열과 달리 제각각이라는 점이다.
이때 정적 메모리 내용을 다룰 때 보았던, 메모리 주소의 경계 정렬 문제가 발생한다.
스택에서는 .align 같은 키워드를 사용할 수 없으니 직접 수동으로 정렬된다.
이때 정렬의 기준은 제일 먼저 선언된 a를 기준으로 정렬된다.
이렇게 a를 시작으로 하여 상대적인 위치를 잡아나간다.
char 은 1의 배수가 되도록
short 는 2의 배수가 되도록
int 는 4의 배수가 되도록 주소값을 잡아준다.
이제 이를 통해 이 구조체가 총 24byte의 공간을 차지하고 있다는 것을 알 수 있다.
따라서 구조체 변수 하나당, 스택에는 24byte의 공간이 추가로 필요하다.
그리고 이 구조체에 접근할 때, 구조체의 시작 위치는 %fp - 24 가 된다.
그런데 구조체의 주소를 고려할 때는 한가지 더 고려해야 할 것이 있다.
struct s {
int a, b;
char c;
short d, e;
int f;
short g;
}
만약 구조체가 위와 같은 구조를 가진다면 어떻게 될까?
(기존 구조체에서 마지막 변수의 크기만 4바이트에서 2바이트로 줄어들었다.)
단순히 이렇게 줄어들게 하면 되는걸까?
정답은 X다.
지금 이 그림은 '상대적인 주소' 차이만을 고려하여 정렬한 상태이다.
이제 실제 메모리에 넣을 때는 '절대적인 주소' 값까지 고려를 해주어야 하는데,
이는 '구조체에 존재하는 변수 중 가장 큰 사이즈의 배수' 로 구조체 시작 위치를 맞춰주어야 한다.
지금 위 그림과 같이 맞추면 구조체의 시작주소가 %fp - 22 가 되는데, %fp-22 는 2의 배수이지 4의배수가 아니므로
%fp-22 위치에서부터 4byte 크기인 a 변수의 값을 읽을 때 문제가 발생한다.
따라서 아래 그림과 같이 정렬하여야 한다.
정리해보면
1. 구조체에서 변수가 선언된 순서대로 낮은주소부터 높은 주소로 공간을 차지해나간다.
2. 이때 offset을 각 변수의 배수에 맞게 맞춰준다.
3. 전체 구조체의 총 크기를 알았다면, 구조체의 선언 위치를 해당 크기를 구조체의 변수 사이즈 중 가장 큰 사이즈의 배수로 맞춘다.
예제
다음과 같은 C 코드를 어셈블리로 옮겨보자.
int main() {
struct student{
char name[20];
int score_math;
int score_eng;
int average;
};
struct student s = {"kim", 95, 98, 0};
s.average = (s.score_math + s.score_eng)/2;
}
학생의 이름과 수학/영어 점수, 평균을 저장하는 구조체를 선언하였다.
새로운 학생 구조체 변수에 값을 할당하고, 할당된 값을 읽어 평균을 계산하는 프로그램이다.
역시 먼저 하던대로 스택 프레임 사이즈부터 계산해보자.
함수 호출이 없으므로, 64 + 지역변수의 크기가 된다.
지역변수의 크기를 계산하기 위해, 구조체가 메모리상 차지하는 공간을 따져보아야 한다.
우선 1byte char 변수가 20개 있는 배열이 있으므로, 20바이트가 사용된다. (offset 0 ~ 20)
다음으로 4byte 변수가 3개 연속으로 선언된다. (offset 20 ~ 32)
offset 사이에 정렬은 필요하지 않으므로 구조체 하나가 사용하는 byte 크기는 32 byte 이다.
구조체 내 변수 중 가장 큰 사이즈는 4byte 인 int 이다.
32 byte는 이미 4의 배수이므로 추가적으로 더 맞춰줄 것이 없다.
따라서 위 구조체는 %fp - 32 으로 접근할 수 있다.
지역변수의 크기를 구하였으니 스택 프레임의 사이즈는 64 + 32 = 96 에서 8의 배수로 맞춰준 96 byte가 된다.
하지만 어셈블리 코드를 작성할 때, 결과값을 출력해보기 위해 스택 프레임의 사이즈를 92byte + 32 byte로 잡아 코딩하겠다.
이 경우, 92 byte + 32 byte = 124 byte 이고, 8의 배수로 맞춰주면 스택 프레임의 사이즈는 128 byte 가 된다.
우선 구조체 내에서 접근을 편리하게 하기 위해 offset 을 상수값으로 선언한다.
위에서 계산한대로 스택 프레임 사이즈를 잡아 스택을 선언해준다.
스택 내 student 구조체 변수 공간에 값을 넣어 초기화 해준다.
문자열은 문자 하나하나와 문자열의 끝을 나타내는 널문자를 넣어 초기화한다.
구조체에서 값을 읽어와 평균을 계산하고, 구조체에 넣어준다.
출력을 위해 포맷 문자열을 미리 잡아주고
계산한 평균값을 출력해준다.
사실 위 예제의 경우 직접 수동으로 각 변수값이 어디에 들어가는지 주소 값을 계산해서 넣어주었지만,
컴파일러가 컴파일할 때는, 위에 적었던 구조체 변수의 메모리내 할당 규칙에 맞게 알아서 계산해서 넣어준다.
경계정렬의 기준이 %fp가 아니라 offset 0 위치부터라는 점을 꼭 기억하자.
'CS > 어셈블리' 카테고리의 다른 글
[SPARC] 31. Register Set & Register Window Over/Underflow (2) | 2023.12.02 |
---|---|
[SPARC] 30. 서브루틴 개요 & call, jmpl, ret, retl (0) | 2023.12.01 |
[SPARC] 28. 다차원 배열과 이진수 곱셈 계산 (2) | 2023.11.29 |
[SPARC] ld: fatal: relocation error: ~~ symbol .data (section): value ~ does not fit 해결 방법 (0) | 2023.11.26 |
[SPARC] 27. 일차원 배열 (0) | 2023.11.24 |