[C#] 스택의 목적은 무엇입니까? 왜 필요한가요?

그래서 지금 C # .NET 응용 프로그램을 디버깅하는 방법을 배우기 위해 MSIL을 배우고 있습니다.

나는 항상 궁금해했다 : 스택의 목적은 무엇입니까?

내 질문을 맥락에서 설명하자면
왜 메모리에서 스택 또는 “로드”로 전송됩니까? 반면에 왜 스택에서 메모리로 또는 “스토리지”로 전송됩니까?
왜 그것들을 모두 메모리에 넣지 않았습니까?

  • 더 빠르기 때문입니까?
  • RAM 기반이기 때문입니까?
  • 효율성을 위해?

CIL 코드를 훨씬 더 깊이 이해할 수 있도록 이것을 이해하려고합니다 .



답변

업데이트 : 나는이 질문을 너무 좋아 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 비트 시스템에서 가상 메모리 관리 를 구현할 수 있다는 엄청난 이점이있었습니다 . 게임은 필요할 때 디스크에서 코드를 페이징하고 새로운 코드를로드 할 때 버릴 수 있기 때문에 메모리에 맞는 것보다 더 클 수 있습니다.


답변

MSIL에 대해 이야기 할 때는 가상 컴퓨터에 대한 지침에 대해 이야기하고 있습니다. .NET에서 사용되는 VM은 스택 기반 가상 머신입니다. 레지스터 기반 VM과 달리 Android 운영 체제에서 사용되는 Dalvik VM 이 그 예입니다.

VM의 스택은 가상이며, VM 명령어를 프로세서에서 실행되는 실제 코드로 변환하는 것은 인터프리터 또는 적시 컴파일러에 달려 있습니다. .NET의 경우 거의 항상 지터 인 MSIL 명령어 세트는 시작부터 지칠 수 있도록 설계되었습니다. 예를 들어 Java 바이트 코드와 달리 특정 데이터 유형에 대한 작업에 대한 고유 한 명령이 있습니다. 해석하기에 최적화되었습니다. MSIL 인터프리터는 실제로 존재하지만 .NET Micro Framework에서 사용됩니다. 리소스가 매우 제한된 프로세서에서 실행되는 머신 코드를 저장하는 데 필요한 RAM을 감당할 수 없습니다.

실제 머신 코드 모델은 스택과 레지스터가 모두 혼합되어 있습니다. JIT 코드 최적화 프로그램의 큰 작업 중 하나는 스택에 유지되는 변수를 레지스터에 저장하여 실행 속도를 크게 향상시키는 방법을 고안하는 것입니다. Dalvik 지터에는 반대의 문제가 있습니다.

머신 스택은 프로세서 설계에서 오랫동안 사용되어 온 매우 기본적인 스토리지 기능입니다. RAM이 제공 할 수있는 것보다 훨씬 빠른 속도로 데이터를 씹어 재귀를 지원하는 최신 CPU에서 매우 중요한 기능인 참조의 로컬 성이 매우 우수합니다. 언어 디자인은 스택을 가지고 지역 변수와 메소드 본문에 제한되는 범위를 지원할 수있는 스택에 의해 크게 영향을받습니다. 스택의 중요한 문제는이 사이트의 이름입니다.


답변

이것에 대해 매우 흥미롭고 자세한 Wikipedia 기사, 스택 머신 명령어 세트의 장점 이 있습니다. 나는 그것을 완전히 인용해야하기 때문에 단순히 링크를 넣는 것이 더 쉽습니다. 난 그냥 자막을 인용합니다

  • 초소형 객체 코드
  • 간단한 컴파일러 / 간단한 인터프리터
  • 최소 프로세서 상태

답변

스택 질문에 조금 더 추가하십시오. 스택 개념은 산술 논리 장치 (ALU)의 기계 코드가 스택에있는 피연산자에서 작동하는 CPU 설계에서 파생됩니다. 예를 들어 곱하기 연산은 스택에서 두 개의 최상위 피연산자를 가져 와서 여러 개를 곱한 다음 결과를 스택에 다시 배치 할 수 있습니다. 기계 언어에는 일반적으로 스택에서 피연산자를 추가하고 제거하는 두 가지 기본 기능이 있습니다. 푸시와 팝. 많은 CPU의 dsp (디지털 신호 프로세서)와 머신 콘트롤러 (세탁기 제어와 같은)에서 스택은 칩 자체에 있습니다. 이를 통해 ALU에보다 빠르게 액세스하고 필요한 기능을 단일 칩으로 통합합니다.


답변

스택 / 힙의 개념을 따르지 않고 데이터가 임의의 메모리 위치에로드되거나 임의의 메모리 위치에서 데이터가 저장된 경우 매우 구조화되지 않고 관리되지 않습니다.

이러한 개념은 성능, 메모리 사용량을 개선하기 위해 데이터 구조를 사전 정의 된 구조에 저장하는 데 사용됩니다.


답변

연속 전달 스타일 의 코딩 을 사용하여 스택없이 시스템을 작동시킬 수 있습니다 . 그런 다음 호출 프레임은 가비지 수집 힙에 할당 된 연속이됩니다 (가비지 수집기는 일부 스택이 필요함).

Andrew Appel의 오래된 저서 : 연속가비지 콜렉션으로 컴파일하는 것이 스택 할당보다 빠를 수 있음을 참조하십시오.

(캐시 문제로 인해 오늘날 약간 잘못되었을 수 있습니다)


답변

나는 “인터럽트 (interrupt)”를 찾고 아무도 그것을 이점으로 포함시키지 않았다. 마이크로 컨트롤러 또는 기타 프로세서를 방해하는 각 장치에 대해 일반적으로 스택으로 푸시되는 레지스터가 있으며 인터럽트 서비스 루틴이 호출되며, 완료되면 레지스터가 스택에서 다시 튀어 나와 원래 위치로 되돌아갑니다. 이었다. 그런 다음 명령 포인터가 복원되고 인터럽트가 발생하지 않은 것처럼 정상적인 활동이 중단 된 위치에서 픽업됩니다. 스택을 사용하면 실제로 여러 장치 (이론적으로) 서로 인터럽트 할 수 있으며 스택 때문에 모두 작동합니다.

연결 언어 라는 스택 기반 언어 제품군도 있습니다 . 스택은 전달 된 암시 적 매개 변수이며 변경된 스택은 각 함수의 암시 적 반환이기 때문에 모두 기능적 언어입니다. 두 넷째요소 (우수) 다른 사람과 함께 예입니다. 팩터는 스크립팅 게임에 Lua와 유사하게 사용되었으며 현재 Apple에서 일하는 천재 인 Slava Pestov가 작성했습니다. YouTube에서 그의 Google TechTalk를 몇 번 봤습니다. 그는 보아 생성자에 대해 이야기하지만 그가 무엇을 의미하는지 잘 모르겠습니다. ;-).

실제로 JVM, Microsoft의 CIL 및 Lua 용으로 작성된 것과 같은 현재 VM 중 일부는 스택 기반 언어로 작성되어 더 많은 플랫폼에 이식 가능해야한다고 생각합니다. 이 연결 언어는 VM 제작 키트 및 이식성 플랫폼으로서의 부름이 어떻게 든 누락되었다고 생각합니다. ANSI C로 작성된 “휴대용”포스 인 pForth도 훨씬 더 보편적 인 휴대 성을 위해 사용될 수 있습니다. Emscripten 또는 WebAssembly를 사용하여 컴파일하려고 시도한 사람이 있습니까?

스택 기반 언어에는 매개 변수를 전혀 전달하지 않고 호출 할 함수를 나열 할 수 있기 때문에 영점이라는 코드 스타일이 있습니다. 함수가 완벽하게 결합되면 모든 영점 함수 목록 만 있으면됩니다 (이론적으로). Forth 또는 Factor를 자세히 살펴보면 내가 말하는 내용을 볼 수 있습니다.

에서 넷째 쉬운 , 자바 스크립트로 작성된 좋은 온라인 자습서, 여기에 작은 샘플 (주의는 “평방 제곱 평방 스퀘어”제로 포인트 스타일을 호출의 예와 같은)이다 :

: sq dup * ;  ok
2 sq . 4  ok
: ^4 sq sq ;  ok
2 ^4 . 16  ok
: ^8 sq sq sq sq ;  ok
2 ^8 . 65536  ok

또한 Easy Forth 웹 페이지 소스를 보면 하단에 약 8 개의 JavaScript 파일로 작성된 매우 모듈 식이라는 것을 알 수 있습니다.

나는 Forth를 동화하려는 시도로 손을 잡을 수있는 모든 Forth 책에 많은 돈을 썼지 만 이제는 더 잘 이해하기 시작했습니다. 나는 당신이 정말로 그것을 얻기를 원한다면 (이것이 너무 늦게 발견되면) FigForth에 관한 책을 가져 와서 구현하십시오. 상업용 Forth는 모두 너무 복잡하며 Forth의 가장 큰 장점은 전체 시스템을 위에서 아래로 이해할 수 있다는 것입니다. 어쨌든 Forth는 새로운 프로세서에서 전체 개발 환경을 구현하지만 필요한 경우그것이 모든 것을 C와 함께 전달하는 것처럼 보였으므로, Forth를 처음부터 작성하는 통과 의례로 여전히 유용합니다. 따라서이 작업을 선택하면 FigForth 책을 사용해보십시오. 여러 프로세서에서 동시에 여러 Forth가 구현되어 있습니다. 일종의 로제 타석.

효율성, 최적화, 제로 포인트, 인터럽트시 레지스터 저장 및 재귀 알고리즘의 경우 스택이 필요한 이유는 “올바른 모양”입니다.