CIL 어셈블리 언어

지역 변수

3.7) 지역 변수

CIL이 추가적인 변수 선언을 허용하지 않는다고 하였다하지만 우리는 프로시저 내에 지역 변수를 만들어 사용할 수 있는데그 방법은 다음과 같다.

지역 변수로 사용할 공간을 확보한다.

확보한 공간의 주소를 기억해놓고필요할 때마다 해당 주소에 접근한다.

다음은 이를 구현하는 코드이다이전에 사용하지 않던 기본 변수를 사용하므로 주의 깊게 봐야 한다.

LocalVariable.c

#include "CIL.h"

STRING sNewLine = "\n";

 

// main

PROC(main)

 

// sp는 현재 메모리의 위치를 표시하는 기본 변수입니다.

// sp의 값을 정수로 출력하고 개행합니다.

PUSH(sp)

INVOKE(print_int)

PUSH(sNewLine)

INVOKE(print_str)

 

// sp 기본 변수의 값을 4만큼 뺍니다.

// sp -= 4;

// 4byte는 32bit 정수형 변수의 크기입니다.

SUB(sp, 4)

 

// sp가 가리키는 메모리의 주소 값을 a에 복사합니다.

// a = &m[sp];

// CIL은 자료형에 엄격하지 않습니다.

LEA(a, m + sp)

 

// a의 값을 주소 값으로 간주하고 해당 주소에 값을 설정합니다.

// *a = 10;

SETL(a, 10)

 

// sp가 가리키는 메모리의 값을 획득하여 a에 복사합니다.

GETL(a, m + sp)

 

// a의 값을 출력합니다.

PUSH(a)

INVOKE(print_int)

 

ENDP

값을 출력하는 코드를 제외하면 사실 4줄밖에 안 되는 단순한 코드다다만 여기에서 메모리를 좀 더 명확하게 그림으로 나타내어야 이후에 이야기를 진행할 때 문제가 없을 것 같다.

이미 알고 있겠지만메모리는 바이트의 배열이다. CIL에서는 메모리에 접근하기 위한 기본 변수를 제공하는데,이 변수에 대해 먼저 정리하자.

- m: memory. 시스템의 메모리를 나타내는 배열 변수다.

- sp: stack pointer. 프로그램 실행 시에 생성되는 스택 메모리를 가리키는 포인터 변수다.

- bp: base pointer. 프로시저 호출 시에 스택의 시작 주소를 저장하는 포인터 변수다.

bp에 대해서는 좀 더 나중에 다루고일단 m과 sp만을 얘기해보자스택이라고 하니 1장에서 배웠던 스택을 떠올릴 수 있는데맞는 판단이고 실제로 스택 형태로 메모리가 관리되지만여기서는 일단 스택 영역이라는 메모리 배열이 선언되어있고 sp가 이 배열을 가리키는 형태라고 생각하는 게 이해하기 편할 것 같다(물론 후에 왜 이것이 스택인지를 설명할 것이다). 독자의 편의를 위해 위에 제시한 코드에서 값을 출력하는 등의 쓸모없는 부분을 제외한 코드를 보이겠다.

LocalVariable.c

#include "CIL.h"

PROC(main)

SUB(sp, 4)

LEA(a, m + sp)

SETL(a, 10)

GETL(a, m + sp)

ENDP

프로시저가 호출되면 메모리는 다음 상태가 된다.

이제 스택 포인터에서 값을 빼는 SUB 연산을 수행한다.

그리고 sp가 가리키는 메모리의 주소를 LEA(load effective address) 명령으로 획득한다.

SETL 명령을 이용하여 획득한 주소에 10을 저장한다.

마지막으로 GETL 명령을 이용하여 기본 변수 a, sp가 가리키는 메모리의 값을 획득하여 저장한다.

지역 변수를 두 개 이상 사용해야 하는 경우에는 스택의 시작 주소를 기준으로 변수의 크기만큼을 뺀다여기서 용어를 하나 정의하자어떤 두 대상의 수치적 거리를 오프셋(offset)이라고 한다따라서 위 그림에서 기본 변수sp와 bp의 오프셋은 4바이트가 된다이는 아주 중요한 내용인데이 예제에서는 만든 지역 변수에 접근하기 위해LEA 명령을 이용할 때 sp 기본 변수를 이용했지만일반적으로는 기본 변수 bp와 해당 지역 변수의 오프셋의 합을 이용한다예를 들어 이 예제에서는 다음이 성립한다.

만든 지역 변수의 위치 == sp == (bp - 4)

따라서 LEA 명령을 다음과 같이 수행해도 문제되지 않는다.

이것이 왜 중요하냐면지역 변수의 위치를 계산할 때는 반드시 bp를 이용해야 하기 때문이다지역 변수를 두 개 만드는 상황을 가정하자.

LocVarSp.c

#include "CIL.h"

STRING sNewLine = "\n";

 

// main

PROC(main)

 

// 4바이트 지역 변수를 생성합니다임시로 var1이라고 합시다.

// int var1;

// 현재 var1과 sp의 오프셋은 0byte입니다.

SUB(sp, 4)

 

// var1 = 10;

// sp는 현재 var1을 가리킵니다.

SETL(m + sp, 10);

 

// 4바이트 지역 변수를 더 생성합니다임시로 var2라고 합시다.

// int var2;

// 현재 var2와 sp의 오프셋은 0byte입니다.

// 현재 var1과 sp의 오프셋은 4byte입니다.

SUB(sp, 4)

 

// var2 = 20;

// sp는 현재 var2를 가리킵니다.

SETL(m + sp, 20);

 

// sp를 기준으로 var1과 var2의 값을 획득합니다.

GETL(a, m + sp); // (sp)가 가리키는 값을 획득합니다.

GETL(b, m + sp + 4); // (sp+4)가 가리키는 값을 획득합니다.

 

ENDP

이 코드의 문제점이 무엇인지 알겠는가바로 변수가 새롭게 생성됨에 따라같은 변수를 가리키는 데 sp와의 오프셋이 계속 달라진다는 점이다. C는 변수 선언이 함수의 앞에 모두 위치하므로 크게 문제되지 않는다고 할지 모르나, sp는 지역 변수를 선언하는 데만 사용되는 변수가 아니다프로시저 호출을 위한 인자를 전달할 때프로시저를 호출할 때도 sp를 사용한다즉 sp를 이용하여 지역 변수에 접근하려면 프로시저 내에서 언제 그 값이 변하는지를 몽땅 추적해서 그 오프셋을 이용해야 한다.

이 문제는 스택의 시작 주소를 보관하는 bp 기본 변수를 이용하면 해결할 수 있다다음은 위 코드를 bp 기본 변수를 이용하여 변경한 것이다.

LocVarBp.c

#include "CIL.h"

STRING sNewLine = "\n";

 

// main

PROC(main)

 

// 4바이트 지역 변수를 생성합니다임시로 var1이라고 합시다.

// int var1;

// 현재 var1과 bp의 오프셋은 4byte입니다.

SUB(sp, 4)

 

// var1 = 10;

// (bp-4)는 var1을 가리킵니다.

SETL(m + bp - 4, 10);

 

// 4바이트 지역 변수를 더 생성합니다임시로 var2라고 합시다.

// int var2;

// 현재 var2와 bp의 오프셋은 8byte입니다.

// 현재 var1과 bp의 오프셋은 4byte입니다.

SUB(sp, 4)

 

// var2 = 20;

// (bp-8)은 var2를 가리킵니다.

SETL(m + bp - 8, 20);

 

// sp를 기준으로 var1과 var2의 값을 획득합니다.

GETL(a, m + bp - 4); // var1 값을 획득합니다.

GETL(b, m + bp - 8); // var2 값을 획득합니다.

 

ENDP

이는 프로시저에서 임의의 위치에 변수를 선언하더라도해당 변수의 위치와 스택 메모리의 시작 지점의 값이 바뀌지 않음을 이용한 것이다. (bp-4)는 언제나 var1이며, (bp-8)은 언제나 var2이다이로써 같은 변수를 가리키기 위해 매번 sp를 추적해야 할 필요가 말끔히 사라졌다따라서 우리는 지역 변수에 접근할 때 sp 변수가 아닌 bp 변수를 이용해서 접근하는 것이 바람직하다.

댓글

댓글 본문
graphittie 자세히 보기