업데이트 : 나는이 질문을 너무 좋아 2011 년 11 월 18 일 내 블로그의 주제 로 만들었습니다 . 좋은 질문 감사합니다!
나는 항상 궁금해했다 : 스택의 목적은 무엇입니까?
런타임에 실제 스레드 당 스택 이 아니라 MSIL 언어 의 평가 스택 을 의미한다고 가정합니다 .
메모리에서 스택 또는 "로드"로 전송되는 이유는 무엇입니까? 반면에 왜 스택에서 메모리로 또는 "스토리지"로 전송됩니까? 왜 그것들을 모두 메모리에 넣지 않았습니까?
MSIL은 "가상 머신"언어입니다. C # 컴파일러와 같은 컴파일러는 CIL을 생성 한 다음 런타임에 JIT (Just In Time) 컴파일러라는 또 다른 컴파일러가 IL을 실행할 수있는 실제 기계 코드로 변환합니다.
먼저 "왜 MSIL이 있습니까?"라는 질문에 대답하겠습니다. C # 컴파일러가 머신 코드를 작성하는 이유는 무엇입니까?
이런 식으로 하는 것이 더 저렴 하기 때문입니다 . 그렇게하지 않았다고 가정하자. 각 언어에 자체 기계 코드 생성기가 있어야한다고 가정하십시오. C #, JScript .NET , Visual Basic, IronPython , F # 등 20 개의 언어 가 있으며 10 개의 서로 다른 프로세서가 있다고 가정합니다. 몇 개의 코드 생성기를 작성해야합니까? 20 x 10 = 200 코드 생성기. 많은 작업입니다. 이제 새 프로세서를 추가하려고한다고 가정하십시오. 각 언어마다 하나씩 코드 생성기를 20 번 작성해야합니다.
또한 어렵고 위험한 작업입니다. 전문가가 아닌 칩을위한 효율적인 코드 생성기를 작성하는 것은 어려운 일입니다! 컴파일러 설계자는 새로운 칩 세트의 효율적인 레지스터 할당이 아니라 언어의 의미 론적 분석에 대한 전문가입니다.
이제 CIL 방식으로 수행한다고 가정하겠습니다. 몇 개의 CIL 생성기를 작성해야합니까? 언어 당 하나. 몇 개의 JIT 컴파일러를 작성해야합니까? 프로세서 당 1 개 합계 : 20 + 10 = 30 개의 코드 생성기. 또한 CIL은 간단한 언어이기 때문에 CIL 생성기는 쓰기가 쉽고 CIL은 간단한 언어이기 때문에 CIL- 기계 코드 생성기는 쓰기도 쉽습니다. 우리는 C #과 VB의 모든 복잡성을 없애고 지터를 작성하기 쉬운 간단한 언어로 모든 것을 "낮게"줄입니다.
중간 언어를 사용하면 새로운 언어 컴파일러를 생산하는 비용이 크게 줄어 듭니다 . 또한 새로운 칩 지원 비용을 대폭 절감합니다. 새로운 칩을 지원하고 싶을 때, 그 칩에 대한 전문가를 찾아 CIL 지터를 작성하게하세요. 그런 다음 칩에서 모든 언어를 지원합니다.
우리는 왜 MSIL을 가지고 있는지를 설정했습니다. 중간 언어를 사용하면 비용이 낮아지기 때문입니다. 그렇다면 왜 언어가 "스택 머신"입니까?
스택 머신은 개념적으로 언어 컴파일러 작성자가 다루기 매우 단순하기 때문입니다. 스택은 계산을 설명하기위한 간단하고 이해하기 쉬운 메커니즘입니다. 스택 머신은 개념적으로 JIT 컴파일러 작성자가 다루기 매우 쉽습니다. 스택 사용은 단순화 된 추상화이므로 비용이 절감 됩니다.
당신은 "왜 스택을 가지고 있습니까?" 모든 것을 메모리에서 직접 수행하지 않는 이유는 무엇입니까? 글쎄, 그것에 대해 생각해 보자. 다음에 대한 CIL 코드를 생성한다고 가정하십시오.
int x = A() + B() + C() + 10;
"add", "call", "store"등이 항상 인수를 스택에서 가져 와서 스택에 결과 (있는 경우)를 넣는 규칙이 있다고 가정합니다. 이 C #에 대한 CIL 코드를 생성하기 위해 다음과 같이 말합니다.
load the address of x // The stack now contains address of x
call A() // The stack contains address of x and result of A()
call B() // Address of x, result of A(), result of B()
add // Address of x, result of A() + B()
call C() // Address of x, result of A() + B(), result of C()
add // Address of x, result of A() + B() + C()
load 10 // Address of x, result of A() + B() + C(), 10
add // Address of x, result of A() + B() + C() + 10
store in address // The result is now stored in x, and the stack is empty.
이제 스택없이 수행했다고 가정 해보십시오. 모든 opcode가 피연산자의 주소와 결과를 저장하는 주소를 취하는 방식으로 수행합니다 .
Allocate temporary store T1 for result of A()
Call A() with the address of T1
Allocate temporary store T2 for result of B()
Call B() with the address of T2
Allocate temporary store T3 for the result of the first addition
Add contents of T1 to T2, then store the result into the address of T3
Allocate temporary store T4 for the result of C()
Call C() with the address of T4
Allocate temporary store T5 for result of the second addition
...
어떻게되는지 봤어? 일반적으로 규칙에 따라 모든 임시 저장소를 명시 적으로 할당해야하기 때문에 코드가 엄청나게 커지고 있습니다. 더 나쁜 것은, 우리의 opcode 자체는 모두 결과가 쓰여질 주소와 각 피연산자의 주소를 인수로 취해야하기 때문에 모두 엄청나게 커지고 있다는 것입니다. 스택에서 두 가지 작업을 수행하고 한 가지 작업을 수행한다는 것을 알고있는 "추가"명령은 단일 바이트 일 수 있습니다. 두 개의 피연산자 주소와 결과 주소를 취하는 add 명령어는 엄청납니다.
스택은 일반적인 문제를 해결하기 때문에 스택 기반 opcode를 사용 합니다. 즉 : 임시 저장 공간을 할당하고 곧 사용하고 나면 빨리 제거합니다 . 처리에 스택이 있다고 가정함으로써 opcode를 매우 작게 만들고 코드를 간결하게 만들 수 있습니다.
업데이트 : 몇 가지 추가 생각
덧붙여서, (1) 가상 머신 지정, (2) VM 언어를 대상으로하는 컴파일러 작성 및 (3) 다양한 하드웨어에서 VM 구현을 작성함으로써 비용을 대폭 절감한다는 아이디어는 전혀 새로운 아이디어가 아닙니다. . MSIL, LLVM, Java 바이트 코드 또는 다른 최신 인프라에서는 시작되지 않았습니다. 내가 아는이 전략의 초기 구현은 1966 년 의 pcode 시스템입니다 .
내가 개인적으로이 개념에 대해 들어 본 첫 번째는 Infocom 구현자가 Zork을 여러 다른 컴퓨터에서 잘 작동 시키는 방법을 알게되었을 때였습니다 . Z- machine이라는 가상 머신을 지정한 다음 게임을 실행하려는 모든 하드웨어에 대해 Z-machine 에뮬레이터를 만들었습니다. 이는 원시 8 비트 시스템에서 가상 메모리 관리 를 구현할 수 있다는 엄청난 이점이있었습니다 . 게임은 필요할 때 디스크에서 코드를 페이징하고 새로운 코드를로드 할 때 버릴 수 있기 때문에 메모리에 맞는 것보다 더 클 수 있습니다.