C 인터프리터를 Javascript로 구현하는 도중에... 뭔가 메모리에 대한 종합적인 궁금증이 생겼습니다.

HDNua의 이미지

안녕하세요. 뜸하게 나타나는 군인입니다.
제목에 인터프리터라고 썼지만 실제로는 가짜 컴파일러쯤 되는 게 나올 것 같은데,
KLDP 여러분들의 조언이 필요하여 질문드려봅니다.

-----
1. 서론
저는 현재 부대 휴게실 컴퓨터로 Javascript를 이용하여 C 인터프리터를 개발하고 있습니다.
(이에 대한 자세한 이유는 http://kldp.org/node/140925)
결론은, 프로그램을 바로 개발하는 것은 안 되니까
코드를 작성하면 그 실행 결과만 텍스트로 보여주는 가짜 해석기를 만들자는 생각에서 출발하였습니다.

실제 위와 같은 프로그램을 아직 개발하지는 못했지만, 현재 선언과 식을 인식하고
변수나 함수를 선언하여 리스트에 저장하는 코드(dcl), 식을 후위 연산식으로 바꾸어 계산해주는 코드(rdx: read expression)는 작성하였습니다.
dcl은 'The C Programming Language - Dennis M. Ritchie & Brian W. Kernighan'을 참고하여 작성했고
rdx는 '윤성우 저 열혈 자료구조 - 윤성우'를 참고하여 작성했습니다.

문제가 나타난 건 함수를 구현하는 부분이었는데, 이에 대한 아이디어가 떠오르지 않았습니다.
여러 번 고민해본 결과는 차라리 어셈블리로 변환하는 게 더 쉽겠다는 것이었는데,
막상 그렇게 구현이라도 해보려니 어셈블리에 대한 지식도 부족하고 무엇보다 메모리에 대한 고민이 생겨서
KLDP에 질문을 올리게 되었습니다.

그 전에 먼저, 이런 중구난방인 글을 읽도록 부탁드리는 것에 죄송하다는 말씀을 올리고 싶습니다.

3. 질문하려는 것
- 프로그램은 코드, 데이터, 스택, 힙의 네 영역이 있어서 이것을 이용해 돌아간다는 설명을 책으로 봤습니다. (from '윤성우 저 열혈 C 프로그래밍 - 윤성우')
처음에는 프로그램에 코드, 데이터, 스택, 힙을 다 집어넣는다는 생각을 갖고 있었는데, 생각해보니
스택과 힙이 다 들어간다면 탈출 조건이 없는 재귀 함수나, 무한히 동적 할당하는 코드는 작성하는 것만으로 용량을 다 잡아먹는다는 생각이 들어
프로그램 파일에는 코드와 데이터만 들어가도 되겠다는 생각을 했습니다.
그러다보니 궁금해진 것인데, 운영체제가 어떤 프로그램이 사용할 메모리의 최댓값을 알고 있다는 건 말이 안 된다는 생각이 들었습니다.
그렇다면 운영체제는 프로그램을 실행할 때마다 각각의 프로그램이 사용할 수 있는 메모리의 양을 정해줄 것이라는 생각이 들었는데 이게 맞는 건가요?
만약 그렇다면 엄청나게 메모리를 사용하는 거대한 프로그램은 운영체제에 따라 실행할 수 없는 상황도 있을 수 있나요?

- 작성한 코드를 어셈블리로 변환해보려는 생각에 CPU가 무엇인지 찾아봤습니다.
검색 결과로 알아낸 건 32비트 cpu는 eax, ebx, ecx, edx, ebp, esp, edi, esi와 같은 접근/변경 가능한 레지스터와 ip와 같은 접근/변경 불가능한 레지스터가 있다는 정도인데,
문득 Windows와 같이 멀티프로세스를 지원하는 운영체제에서 멀티태스킹이 가능하려면 모든 프로세스(적합한 용어인지는 모르겠지만 실행되고 있는 프로그램이란 뜻으로 썼습니다)가
각각의 cpu 레지스터 값을 저장하고 있고, 프로세스가 p1, p2, p3와 같이 있으면 p1의 ip가 코드를 실행한 다음 p1의 cpu 저장영역에 복사, 이 과정을 p2, p3, ...에 동일하게
수행하면 멀티프로세스가 가능할 것이라는 생각이 들었습니다. 이건 맞는 건가요?

- 코드 값은 cpu의 ip 레지스터가 갖고 있다는 문서를 읽은 적이 있습니다.
어셈블리를 제대로 공부한 적이 없어 궁금한 점인데, 기계어 명령어는 모두 그 크기가 동일하게 정해져있나요?
이 질문은 좀 모호한 감이 있는데, 저는 ip 레지스터가 명령을 하나 실행하면 실행한 만큼 오프셋을 옮길 것이라는 생각을 갖고 있어서 그렇습니다.
만약 기계어 명령어가 크기가 각각 다르다면, 명령의 끝을 컴퓨터가 인지할 수 있도록 하는 값이 따로 정해져있는 것인가요? (C의 ;와 같이)

- 갑자기 질문할 것이 산더미처럼 생겨서 아무거나 마구 여쭤보고 있습니다. 지금 이 상태에 관련해 검색할 수 있는 키워드나, 참고할 만한 서적을 가르쳐주시면 감사하겠습니다.
일단 이 정도로 질문을 마치고, 나머지는 더 이해하게 되면 여쭤보겠습니다.

4. 진행 상태 (참고)
현재 작성된 프로그램은 다음과 같은 출력이 나옵니다. (//로 나오는 부분은 주석처럼 실제 출력으로 나오는 부분이 아닙니다)
사용자 정의 자료형(user defined type)이나 단항 연산자, 캐스팅도 구현하긴 했지만 싣지 않았습니다.

-----
입력 1:
int num1, num2, num3, max, min;
num1 = 1, num2 = 2;
num3 = num1 + num2;
max = num1 < num2 ? num2 : num1;
min = ((num1 < num2) ? ((num1 < num3) ? num1 : num3) : ((num2 < num3) ? num2 : num3);
 
출력 1:
int num1, num2, num3, max, min;
run.findModule: module: [dcl]                           // run 객체가 findModule 메서드를 호출하여 가장 적합한 모듈을 찾아냅니다. 찾아낸 모듈은 dcl
> add data
[num1 00000000int]
[num2 00000000int]
[num3 00000000int]
[max 00000000int]
[min 00000000int]
 
num1 = 1, num2 = 2;
run.findModule: module: [rdx]                           // 찾아낸 모듈은 rdx
rdx.convert: expression: [num1 1 = num2 2 = ,]          // rdx의 convert 메서드가 호출된 후 변환된 후위 연산식을 보여줌
<<<<< memory >>>>>                                      // 연산 완료 후 메모리 상태를 보여줌
num1: 1 (1000)                                          // 변수_이름: 변수_값 (변수_주소). 이때 변수_주소는 완전 무의미한 값이다
num2: 2 (1004)                                          // 그냥 1000을 시작주소로 잡고 4씩 더하기만 함
num3: 0 (1008)                                          // 16진수도 아님
max: 0 (1012)                                           // 그리고 초깃값은 일단 0으로 줬음
min: 0 (1016)
 
num3 = num1 + num2;
run.findModule: module: [rdx]                           // 간단하게, 앞에 형식(type)/지정자(specifier)/한정자(qualifier)나 typedef가 있으면 dcl, 그 외의 경우 rdx로 판정하게 했다
rdx.convert: expression: [num3 num1 num2 + =]
<<<<<< memory >>>>>
num1: 1 (1000)
num2: 2 (1004)
num3: 3 (1008)
max: 0 (1012)
min: 0 (1016)
 
max = num1 < num2 ? num2 : num1;
run.findModule: module: [rdx]
rdx.convert: expression: [max num1 num2 < 0 ? num1 : num2 ; =] // 삼항 연산자는 책에 예제가 없어 직접 고안했다. a ? b : c라면 a 0 ? b : c ;로 변환해 후위 연산식을 유지하게끔 했음. 근데 0은 필요없을 것 같다
<<<<< memory >>>>>
num1: 1 (1000)                                                 // 해석할 때 참이라면 첫 부분만 읽고 뒷 부분을 스킵하는 옵션을 줌
num2: 2 (1004)                                                 // 거짓이라면 첫 부분에서 스킵하는 옵션을 주고 뒷 부분을 읽음
num3: 3 (1008)
max: 2 (1012)
min: 0 (1016)
 
min = ((num1 < num2) ? ((num1 < num3) ? num1 : num3) : ((num2 < num3) ? num2 : num3);   // 사실 이 부분은 구현된 상태는 아니고 이럴 것이라 예상하는 부분입니다.
run.findModule: module: [rdx]
rdx.convert: expression: [min num1 num2 < 0 ? num1 num3 < 0 ? num1 : num3 ; num2 num3 < 0 ? num2 : num3 ; =]
<<<<< memory >>>>>
num1: 1 (1000)
num2: 2 (1004)
num3: 3 (1008)
max: 2 (1012)
min: 1 (1016)
 
-----
입력 2:
typedef int Var, *Ptr, **PtrPtr, *ArrPtr[], (*PtrArr)[], Func();
Var var;
test t;
const int n1;
static const unsigned scu;
unsigned long long int ull;
extern static int es1;
n1 = 1;
 
출력 2:
typedef int Var, *Ptr, **PtrPtr, *ArrPtr[], (*PtrArr)[], Func(), comp(int num1, int num2), strcmp(const char *, const char *);
run.findModule: module: [dcl]
> add type
[Var 00000000int]
[Ptr *00000000int]
[PtrPtr **00000000int]
[ArrPtr []*00000000int]
[PtrArr *[]00000000int]
[Func ()00000000int]
[comp (00000000int, 00000000int)00000000int]
[strcmp (*00100000char, *00100000char)00000000int]    // 오른쪽으로부터 6번째 비트는 constant 속성
 
Var var;
run.findModule: module: [dcl]
> add data
[var Var]
 
test t;
run.findModule: module: [rdx]
rdx.convert: exception: undeclared identifier 'test'  // 던진 예외는 바로 출력하기도 하고 오류 리스트에 넣어서 해석 종료 후 일괄적으로 보여주기도 한다
 
const int n1;
run.findModule: module: [dcl]
> add data
[n1 00100000int]
 
static const unsigned scu;
run.findModule: module: [dcl]
> add data
[scu 00110100int]                                     // 5번째는 4번째와 더불어 00:auto, 01:extern, 10:static, 11:volatile로 하려고 했다. 나중엔 11은 오류 처리하고 volatile은 다른 비트로 설정하게끔 함
 
unsigned long long int ull;                           // 3번째는 부호. 0:signed, 1:unsigned. 2번째는 1번째와 더불어 00:normal, 01:short, 10:long, 11:long long
run.findModule: module: [dcl]
> add data
[ull 00000111int]
 
extern static int es1;                                // 지정자 중복 오류로 탈출. (11이라서)
run.findModule: module: [dcl]
dcl.dcl: exception: duplicated specifier
 
n1 = 1;
run.findModule: module: [rdx]
rdx.convert: expression: [n1 1 =]
rdx.convert: exception: n1 is const; cannot change value    // const 속성을 보고 1이라서 예외처리
 
-----

이런 이유로 KLDP 여러분의 도움을 구합니다.

mirheekl의 이미지

일단 x86 어셈블리와, 운영체제(또는 시스템 프로그래밍) 관련 책을 보시면 상당부분 궁금증이 해결될 겁니다. 해당 주제를 다루고 있다면 어떤 책이어도 관계없습니다.

1. 최대값은 정해져 있으나 이것을 한꺼번에 할당해주는 게 아니고 그때그때 할당해주며 (힙, 스택을 주로 사용) 메모리가 모두 소진됐을 경우 예외가 발생합니다.

2. 그게 컨텍스트 스위칭입니다.

3. 모두 다릅니다만 어차피 포맷이 정해져 있으므로 다음 명령을 읽어들이는 데에는 별 지장이 없습니다. (C의 ;같은 터미네이터는 없습니다.)
물론 어떠한 이유로든 한번 꼬이면 (명령의 중간부터 읽는다든지) 심각한 문제가 생기겠죠.

헌데 아예 실행파일을 만드실 생각이신가요? 그것이 아니고 그냥 브라우저상에서 실행하거나 결과값을 보는 게 목표라면 멀티프로세싱이나 x86에 대해서는 그다지 고려 안해도 될 듯 합니다. 그리고 이 쪽이 더 작업하기 재미있을 것 같네요. 어셈블리로 변환한다 하더라도 실행파일로 저장할 것이 아니라면 결국 해석기가 있어야 실행이 가능할텐데 이러려면 굳이 x86구조를 따를 필요가 없이 닷넷이나 자바마냥 가상의 머신코드를 본인이 편한 대로 새로 디자인하면 됩니다. 이미 이렇게 하고 계신 거죠? (사실 이렇게 하느니 그냥 처음부터 자바스크립트로 프로그래밍을 하거나 C->자바스크립트 변환기?를 만들어볼 수도 있겠지만.. 애초에 현재 작업을 즐기시는 듯 하니 상관없겠죠.)

처음에는 모든 명령어셋의 길이를 통일시키고 int등의 기본타입만 지원하는 등 단순하게 시작하시고 뭔가 정상동작하는 실체가 나온 다음에 조금씩 개선하면 좋겠죠.

--

HDNua의 이미지

사실 말씀하신 것처럼 프로그래밍을 연마하려면 그냥 자바스크립트를 이용해도 되긴 하는데,
이게 하다보니 재미가 들려서 그냥 C 해석 도구를 만드는 프로젝트로 바뀌었습니다.

아무튼 먼저 해주신 답변에 대한 코멘트.
1. 그러면 프로세스 p1, p2가 있을 때 p1에 할당해준 메모리가 모두 찬 상태라고 하고 p1에서 함수를 호출한다면
p1을 위핸 새로운 메모리를 운영체제가 남은 메모리에서 빼내주는 것이 되나요?
그러니까... 프로세스 하나가 갖는 메모리의 갯수가 하나 이상이 될 수도 있는 건가요?
2. 생각한 개념이 이미 용어로 있다는게... 제법 뿌듯한 일이군요. ^^
3. 제가 궁금했던 건, 기계어로 변환하는 경우 명령어의 크기가 정해져있지 않다면
'아버지 가방에 들어가신다'와 같은 문제가 일어날 수 있다는 생각이 들어서였습니다.
예를 들어 001이 mov, 01이 eax, 100이 4라고 치면 00101100은 mov eax, 2일텐데
0010이 lea, 11이 edx, 00이 0이라면 00101100은 lea edx, 0이 될 수도 있겠지 싶어서,
이런 일이 절대로 일어날 수 없는 일이며 왜 그런가가 궁금해 여쭤봤었습니다.
일단 답변해주신 것처럼 어셈블리로 변환한다면 명령어셋의 길이는 통일하는 방향으로 정했습니다.

아래는 그냥 사설입니다. 하다보니 문제가 많아 머리가 아프다는 내용이고 그냥 답답해서 썼습니다.
소중한 답변 다시 한 번 감사드립니다.

----- 이 부분은 사설입니다. -----
그리고 사실 컴파일러로 전환하게 된 보다 궁극적인 계기는,
아무래도 위에서 한줄씩 읽어내려가는 방식으론 해결할 수 없을 법한 문제가 떠올라서
일단 [num1 00000000int] 같은 거나 [num3 num1 num2 + =]와 같은 가짜 코드를 만들고
이걸 해석하는 방식으로 해결해보자는 생각이 들었기 때문입니다.
(인터프리터로 개발할 시 생기는 문제점이 다음과 같다는 생각을 했습니다.
- 함수가 선언만 되어 정의가 없는 상태에서, 정의가 나타나기 전에 호출이 되는 경우 어떻게 해야 하는가?
이 문제는 비단 함수 뿐만 아니라 사용자 정의 자료형에도 해당하는 문제이기도 합니다.
- 개인적으로 한줄한줄 읽어내려가는 것보다, 한 번 다 변환하고 이걸 읽는 게 안전성도 높고 빠를 것이라는 생각이 들었습니다.)

그러다보니 컴파일러와 링커를 따로 개발하는 것이 좋겠고, 그러려면 통합 개발 환경도 만드는 것이 좋겠고,
파일 입출력도 지원되었으면 좋겠으니 차라리 가짜 운영체제같은 것을 하나 만들고 그 운영체제에서 다 해버리면 어떨까?
(인터넷 정보는 쿠키로 저장한다는 말을 들은 적이 있어서, 이를 이용하면 영구 저장도 가능할 수 있겠다 싶었습니다)
이래버려서 멀티프로세스랑 이것저것 컴파일러와는 좀 다른 궁금증이 있었습니다.

메모리를 신경쓰게 된 건 함수와 포인터를 구현하면서부터입니다.
포인터는 처음에는 주솟값과 일치하는 메모리를 찾아서 그 값을 바꾸면 된다고 생각했는데,
이러면 포인터 연산도 별 의미 없어지고 포인터가 할 수 있는 일이 너무 줄어든다고 생각해서
진짜 주솟값처럼 만들어볼 수 없을까 하여 생각해보다가... 뇌에 제동이 걸렸습니다.

프로그램이 메모리에 올라간 상태, 그러니까 코드/데이터/스택/힙이 메모리에 다 있는 상태를 생각하고 있는데
코드에서 함수의 주솟값 문제도 그렇고 스택과 힙의 크기 문제나 링크 시 외부 코드와 연결하는 문제도 그렇고
갑자기 모든 것이 꽉 막혀버린 느낌입니다.

하...
-------------------------------

저는 이렇게 생각했습니다.

mirheekl의 이미지

1. 여러 프로그램이 한정된 메모리를 나눠쓰기 때문에 애초에 하나씩 똑 떼어서 나눠주기가 곤란하지요.
3. 애초에 그런 혼동이 일어나는 일이 없도록 명령어셋을 만듭니다.

--

HDNua의 이미지

소중한 답변 감사합니다.

저는 이렇게 생각했습니다.

댓글 달기

Filtered HTML

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

BBCode

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param>
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

Textile

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • You can use Textile markup to format text.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Markdown

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • Quick Tips:
    • Two or more spaces at a line's end = Line break
    • Double returns = Paragraph
    • *Single asterisks* or _single underscores_ = Emphasis
    • **Double** or __double__ = Strong
    • This is [a link](http://the.link.example.com "The optional title text")
    For complete details on the Markdown syntax, see the Markdown documentation and Markdown Extra documentation for tables, footnotes, and more.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Plain text

  • HTML 태그를 사용할 수 없습니다.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 줄과 단락은 자동으로 분리됩니다.
댓글 첨부 파일
이 댓글에 이미지나 파일을 업로드 합니다.
파일 크기는 8 MB보다 작아야 합니다.
허용할 파일 형식: txt pdf doc xls gif jpg jpeg mp3 png rar zip.
CAPTCHA
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.