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


320

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

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

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

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

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


28
스택이 메모리의 다른 부분 인 것처럼 스택은 메모리의 한 부분입니다.
코드 InChaos

@CodeInChaos 값 유형과 참조 유형에 대해 이야기하고 있습니까? 아니면 IL 코드 측면에서 동일합니까? ... 스택이 힙보다 더 빠르고 효율적이라는 것을 알고 있습니다 (그러나 값 / 참조 유형의 세계에 있습니다. 여기서 동일한 지 알 수 없습니까?)
Jan Carlo Viray

15
@CodeInChaos-Jan이 참조하는 스택은 함수 호출 중에 스택 프레임을 허용하는 메모리 영역과 달리 IL이 작성되는 스택 시스템이라고 생각합니다. 두 개의 서로 다른 스택이며 JIT 이후 IL 스택은 존재하지 않습니다 (x86에)
Damien_The_Unbeliever

4
MSIL 지식이 .NET 응용 프로그램을 디버깅하는 데 어떻게 도움이됩니까?
Piotr Perak 17:32에

1
현대 컴퓨터에서 코드 캐싱 동작은 성능을 향상시키는 도구입니다. 기억은 어디에나 있습니다. 스택은 보통 여기에 있습니다. 스택이 실제라고 가정하고 일부 코드의 작업을 표현하는 데 사용되는 개념이 아니라고 가정하십시오. MSIL 실행 플랫폼을 구현할 때 스택 개념이 실제로 비트를 밀어 하드웨어에 적용 할 필요는 없습니다.
Reinstate Monica

답변:


441

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


63
와. 그것은 바로 내가 찾던 것입니다. 답변을 얻는 가장 좋은 방법은 주 개발자 자신으로부터 답변을 얻는 것입니다. 시간 내 주셔서 감사합니다. 컴파일러와 MSIL의 복잡함을 궁금해하는 모든 사람에게 도움이 될 것입니다. 고마워 에릭
Jan Carlo Viray

18
좋은 대답이었습니다. Java 사용자인데도 왜 블로그를 읽었는지 알려줍니다. ;-)
jprete

34
@ JanCarloViray : 당신은 매우 환영합니다! 나는 내가주의 수석 개발자,하지 주요 개발자. 이 팀에는 그 직책을 가진 사람들이 여러 명 있는데, 나는 그 중 가장 나이가 많은 사람도 아닙니다.
Eric Lippert

17
@ 에릭 : 코딩을 사랑하는 것을 멈 추면 프로그래머를 가르치는 것을 고려해야합니다. 재미 외에도, 당신은 사업의 압박없이 살인을 할 수 있습니다. 굉장한 감각은 그 지역에서 얻은 것입니다 (그리고 훌륭한 인내심, 추가 할 수도 있습니다). 나는 전직 대학 강사라고 말합니다.
Alan

19
약 4 개의 문단이 제 자신에게 "이것은 Eric처럼 들립니다."라고 5 번째 또는 6 번째로 "Yep, 확실히 Eric"으로 졸업했습니다.
이진 걱정

86

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

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

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

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


2
아주 자세한 설명은 +1, 그리고 +100 (있는 경우 I 수) : 다른 시스템과 언어에 대한 별도의 상세한 비교
월 카를로 Viray

4
Dalvik은 왜 등록 기계입니까? Sicne은 주로 ARM 프로세서를 대상으로합니다. 이제 x86은 동일한 양의 레지스터를 갖지만 CISC이지만 나머지 4 개는 공통 명령어에 내재적으로 사용되기 때문에 그 중 4 개만 로컬 저장에 실제로 사용할 수 있습니다. 반면 ARM 아키텍처에는 로캘을 저장하는 데 사용할 수있는 레지스터가 훨씬 많으므로 레지스터 기반 실행 모델을 용이하게합니다.
Johannes Rudolph

1
@JohannesRudolph 그것은 거의 20 년 동안 사실이 아니 었습니다. 대부분의 C ++ 컴파일러가 여전히 90 년대 x86 명령어 세트를 대상으로한다고해서 x86 자체가 효율적이라는 것을 의미하지는 않습니다. Haswell은 168 개의 범용 정수 레지스터와 168 개의 GP AVX 레지스터를 가지고 있습니다. (현대) x86 어셈블리의 모든 것을 원하는 방식으로 사용할 수 있습니다. 아키텍처 / CPU가 아닌 비난 컴파일러 작성자. 실제로, 중간 컴파일이 그토록 매력적인 이유 중 하나입니다. 주어진 CPU에 가장 적합한 이진 코드 하나입니다. 90 년대의 건축 양식은 없습니다.
Luaan

2
@JohannesRudolph .NET JIT 컴파일러는 실제로 레지스터를 상당히 많이 사용합니다. 스택은 대부분 IL 가상 머신의 추상화이며 실제로 CPU에서 실행되는 코드는 매우 다릅니다. 메소드 호출은 레지스터에 의해 전달되고 로컬은 레지스터로 해제 될 수 있습니다. 머신 코드에서 스택의 주요 이점은 서브 루틴 호출에 대한 격리입니다. 로컬에 레지스터를 넣으면 함수 호출로 인해 당신은 그 가치를 잃어 버리고 실제로 말할 수 없습니다.
Luaan

1
@RahulAgarwal 생성 된 머신 코드는 주어진 로컬 또는 중간 값에 대해 스택을 사용하거나 사용하지 않을 수 있습니다. IL에서 모든 인수와 로컬은 스택에 있지만 머신 코드에서는 사실이 아닙니다 (허용되지만 필수는 아님). 일부는 스택에 유용하며 스택에 배치됩니다. 일부는 힙에 유용하며 힙에 배치됩니다. 어떤 것은 전혀 필요하지 않거나 레지스터에 몇 분만 있으면됩니다. 호출은 완전히 제거되거나 (인라인 된) 인수가 레지스터로 전달 될 수 있습니다. JIT에는 많은 자유가 있습니다.
Luaan

20

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

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

-1 @xanatos 당신이 취한 제목을 시도하고 요약 할 수 있습니까?
Tim Lloyd

@chibacity 내가 요약하고 싶다면 답을했을 것입니다. 나는 아주 좋은 연결 고리를 구하려고 노력했다.
xanatos

@xanatos 나는 당신의 목표를 이해하지만, 큰 위키 백과 기사에 대한 링크를 공유하는 것은 큰 대답이 아닙니다. 인터넷 검색만으로는 찾기가 어렵지 않습니다. 반면에 한스는 좋은 대답을했습니다.
Tim Lloyd

@chibacity OP가 먼저 검색하지 않았기 때문에 게으른 것 같습니다. 여기의 답변자는 설명하지 않고 좋은 연결 고리를 제공했습니다. 두 악은 하나의 선을 행한다.
xanatos

응답자 및 @xanatos +1을 통해 훌륭한 링크를 얻으십시오. 나는 한스는 대답을하지 않은 경우는 그것이 그냥 .. 내가 허용 대답으로 당신을 만들었을 것입니다 .. 요약하고 지식 팩 답을 완벽하게 누군가를 기다리고 있었다 , 단지 링크 가이 없었다 그의 답변에 좋은 노력을 기울인 한스에게 공정한 .. :)
Jan Carlo Viray

8

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


5

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

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


4

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

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

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


0

나는 "인터럽트 (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가 구현되어 있습니다. 일종의 로제 타석.

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

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.